在说享元模式之前来先看一道题:
public static void main(String[] args) { Integer i1 = new Integer(50); Integer i2 = new Integer(50); System.out.println(i1 == i2); Integer i3 = new Integer(500); Integer i4 = new Integer(500); System.out.println(i3 == i4); //需要注意下面这种方式存在隐式装箱 Integer i5 = 50; Integer i6 = 50; System.out.println(i5 == i6); Integer i7 = 500; Integer i8 = 500; System.out.println(i7 == i8); }
很简单对不对?
答案
false false true false
这便是我想说的享元模式。
享元模式英文为:Flyweight,《JAVA与模式》一书中开头是这样描述享元(Flyweight)模式的:
>
Flyweight在拳击比赛中指最轻量级,即“蝇量级”或“雨量级”,这里选择使用“享元模式”的意译,是因为这样更能反映模式的用意。享元模式是对象的结构模式。享元模式以共享的方式高效地支持大量的细粒度对象。
享元即为分享元素,字符串常量池、数据库连接池、缓冲池都是是这个道理。该模式的意图为:运用共享技术有效地支持大量细粒度的对象。 就像上边的例子中,Integer类会把较小的数字保存起来,再次新建比较小的Integer对象时会直接返回该对象的引用,从而避免了再次创建对象。通过查看Integer类的源码我们可以看到Integer会把[-128, 127]之间的数字直接返回共享池中的对象:
public static Integer valueOf(int i) { //IntegerCache.low = -128 //IntegerCache.high = 127 if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
这也是为什么上面例子中第三个输出true的原因。
在单纯的享元模式中,所有的享元对象都是可以共享的。
单纯享元模式所涉及到的角色如下:
上面的Integer例子就是单纯的享元模式
到这里可能有同学会问了:这种引用类型的分享对于final的String和Integer来说倒无所谓,因为被final修饰之后不能再改变,所以如何分享引用都没关系。但是清楚基本类型和引用类型的差别都知道引用类型传引用之后,改变对象的内部数据会导致对象被修改:
int[] a = {1, 2}; int[] b = a; b[0] = 20; System.out.println(a[0]);//同为20
这样该怎么办呢?
别担心,你想到的享元模式也想到了。
先来认识两个概念: 内部状态 :在享元对象内部不随外界环境改变而改变的共享部分。 外部状态 :随着环境的改变而改变,不能够共享的状态就是外部状态。
由于享元模式区分了内部状态和外部状态,所以我们可以通过设置不同的外部状态使得相同的对象可以具备一些不同的特性,而内部状态设置为相同部分。在我们的程序设计过程中,我们可能会需要大量的细粒度对象来表示对象,如果这些对象除了几个参数不同外其他部分都相同,这个时候我们就可以利用享元模式来大大减少应用程序当中的对象。 我们举一个最简单的例子,棋牌类游戏大家都有玩过吧,比如说说围棋和跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色略多一点,但也是不太变化的,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,我们落子嘛,落子颜色是定的,但位置是变化的,所以方位坐标就是棋子的外部状态。 那么为什么这里要用享元模式呢?可以想象一下,上面提到的棋类游戏的例子,比如围棋,理论上有361个空位可以放棋子,常规情况下每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题。
由于享元模式区分了内部状态和外部状态,所以我们可以通过设置不同的外部状态使得相同的对象可以具备一些不同的特性,而内部状态设置为相同部分。在我们的程序设计过程中,我们可能会需要大量的细粒度对象来表示对象,如果这些对象除了几个参数不同外其他部分都相同,这个时候我们就可以利用享元模式来大大减少应用程序当中的对象。
我们举一个最简单的例子,棋牌类游戏大家都有玩过吧,比如说说围棋和跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色略多一点,但也是不太变化的,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,我们落子嘛,落子颜色是定的,但位置是变化的,所以方位坐标就是棋子的外部状态。
那么为什么这里要用享元模式呢?可以想象一下,上面提到的棋类游戏的例子,比如围棋,理论上有361个空位可以放棋子,常规情况下每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题。
复合享元模式UML:
复合享元角色所涉及到的角色如下:
下面就用下棋来举例:
抽象享元类:
//棋子类 public abstract class AbstractChessman { // 棋子坐标 protected int x; protected int y; // 棋子类别(黑|白) protected String chess; public AbstractChessman(String chess) { this.chess = chess; } // 点坐标设置 public abstract void point(int x, int y); // 显示棋子信息 public void show() { System.out.println(this.chess + "(" + this.x + "," + this.y + ")"); } }
具体享元类: 黑色棋子:
//黑色棋子类 public class BlackChessman extends AbstractChessman { /** * 构造方法 初始化黑棋子 */ public BlackChessman() { super("●"); System.out.println("--BlackChessman Construction Exec!!!"); } // 点坐标设置 @Override public void point(int x, int y) { this.x = x; this.y = y; // 显示棋子内容 show(); } }
白色棋子:
//白色棋子 public class WhiteChessman extends AbstractChessman { /** * 构造方法 初始化白棋子 */ public WhiteChessman() { super("○"); System.out.println("--WhiteChessman Construction Exec!!!"); } // 点坐标设置 @Override public void point(int x, int y) { this.x = x; this.y = y; // 显示棋子内容 show(); } }
享元工厂: 棋子工厂类:
import java.util.Hashtable; //棋子工厂 public class ChessmanFactory { // 单例模式工厂 private static ChessmanFactory chessmanFactory = new ChessmanFactory(); // 缓存存放共享对象 private final Hashtable<Character, AbstractChessman> cache = new Hashtable<>(); // 私有化构造方法 private ChessmanFactory() { } // 获得单例工厂 public static ChessmanFactory getInstance() { return chessmanFactory; } /** * 根据字符获得棋子 * * @param c (B:黑棋 W:白棋) * @return */ public AbstractChessman getChessmanObject(char c) { // 从缓存中获得棋子对象实例 AbstractChessman abstractChessman = this.cache.get(c); if (abstractChessman == null) { // 缓存中没有棋子对象实例信息 则创建棋子对象实例 并放入缓存 switch (c) { case 'B': abstractChessman = new BlackChessman(); break; case 'W': abstractChessman = new WhiteChessman(); break; default: break; } // 为防止 非法字符的进入 返回null if (abstractChessman != null) { // 放入缓存 this.cache.put(c, abstractChessman); } } // 如果缓存中存在 棋子对象则直接返回 return abstractChessman; } }
客户端类,即测试类
import java.util.Random; //测试类 public class Client { public static void main(String[] args) { // 创建五子棋工厂 ChessmanFactory fiveChessmanFactory = ChessmanFactory.getInstance(); Random random = new Random(); int radom = 0; AbstractChessman abstractChessman = null; // 随机获得棋子 for (int i = 0; i < 10; i++) { radom = random.nextInt(2); switch (radom) { // 获得黑棋 case 0: abstractChessman = fiveChessmanFactory.getChessmanObject('B'); break; // 获得白棋 case 1: abstractChessman = fiveChessmanFactory.getChessmanObject('W'); break; } if (abstractChessman != null) { abstractChessman.point(i, random.nextInt(15)); } } } }
运行结果:
--BlackChessman Construction Exec!!! ●(0,3) ●(1,0) --WhiteChessman Construction Exec!!! ○(2,1) ●(3,12) ●(4,4) ●(5,9) ●(6,9) ○(7,2) ●(8,11) ○(9,6)
ps:复合享元不可共享,只需继承抽象享元即可,不再演示。
享元模式:
原文链接:https://www.cnblogs.com/lixin-link/p/11104658.html