2019年5月14日22:13:58
java
享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。——《设计模式:可复用面向对象软件的基础》设计模式
Flyweight在拳击比赛中只最轻量级,即“蝇量即”和“雨量级”,这里使用“享元模式”的意译,是由于这样更能反映模式的用意。——《JAVA与模式》数组
享元模式是对象结构型模式之一,此模式经过减小对象的数量,从而改善应用程序所需的对象结构。数据结构
咱们在须要建立大量(例如10^5)的类似的对象时,使用享元模式。反正我历来没有须要建立这么多类似对象的时候,享元模式在真正的应用中用的要比较少,通常是一些底层数据结构使用到。好比,Java中的String。有图:dom
图中的String类实例数就达到了20多万,因此String使用了享元模式。再看看大小,char[]和String对比,差了一个数量级。按道理来讲,char[]和String的大小应该是差很少的啊,为何呢?咱们再看看源码:编辑器
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; //省略 }
String中的char[]就是用来存储字符串的,而后String只是保存着char[]的引用(即这个数组的内存地址)。因此char[]和String数量差很少,可是大小却差了一个数量级的缘由是char[]存储着真正的字符串内容,String只是存储着char[]引用。并且这个char[]放在常量池中,经过享元模式被String引用,这样子一个char[]就可能被多个String共享引用。多说无益,show me code。ide
public class StringDemo { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { String a = "mingmingcome"; String b = "mingmingcome"; System.out.println(a == b); // true System.out.println(a); // mingmingcome String c = new String("mingmingcome"); String d = new String("mingmingcome"); System.out.println(c.equals(d)); // 利用反射修改String中私有成员变量char[] Field value = c.getClass().getDeclaredField("value"); value.setAccessible(true); char[] o = (char[])value.get(c); o[0] = 'L'; o[1] = 'O'; o[2] = 'V'; o[3] = 'E'; System.out.println(a); //LOVEmingcome System.out.println(b); //LOVEmingcome System.out.println(c); //LOVEmingcome System.out.println(d); //LOVEmingcome } }
上面的例子中控制台打印为:字体
true mingmingcome true LOVEmingcome LOVEmingcome LOVEmingcome LOVEmingcome
第一个输出:true说明a
和b
是同一个对象,那a
和b
也共享了同一个char[]。this
第二个输出:mingmingcome是a
改变前的字符串。设计
第三个输出:true,是new出来的String实例对象,equals为true,说明char[]相等。
在咱们后面利用反射修改c
中私有成员变量char[],a
、b
、c
、d
打印输出都为LOVEmingcome,充分说明a
、b
、c
、d
就是使用了享元模式共享了同一个char[]。
享元工厂(Flyweight Factory):一个享元工厂,用来建立并管理Flyweight对象。它主要是用来确保合理地共享Flyweight,当用户请求一个Flyweight是,FlyweightFactory对象提供一个已建立的实例或者建立一个(若是不存在的话)。J
抽象享元(Flyweight):全部具体享元的超类和接口,经过这个接口,Flyweight能够接受并做用于外部状态。
具体享元(Concrete Flyweight):继承Flyweight超类或实现Flyweight接口,并为内部状态增长存储空间。
非共享具体享元(Unshared Concrete Flyweight):指那些不须要共享的Flyweight子类。由于Flyweight接口共享成为可能,但他并不强制共享。
内部状态(intrinsic state):能够用来共享的不变状态,在具体享元模式中
外部状态(extrinsic state):能够做为参数传进来的可变状态
为了搞清楚内部状态和外部状态,咱们来看一个例子。
假设咱们在文本编辑器中输入一个字符,一个Character对象被建立,Character类的属性有名字、字体、大小{name,font,size}。咱们不须要当客户端每次输入一个字符时都建立一个对象,由于字符'B'和另一个字符'B'没有什么不同。若是客户端再一次输入'B',咱们只须要简单地返回咱们以前建立的那个字符'B'对象便可。如今全部这些状态{name,font,size}都是内部状态,由于它们能够在不一样的对象中共享。
咱们Character类给添加更多的属性:行和列,它们说明字符在文本中的位置。这些属性对于相同的字符来讲,也是不一样的,由于没有两个character会在文本中有相同的位置。这些属性被称为外部状态,它们是不能在对象中共享的。
享元模式结构图:
实现:在反恐精英中恐怖分子和反恐精英的建立。咱们有两个类,一个是恐怖分子Terrorist,另外一个是反恐精英Counter Terrorist。当玩家要求一个武器weapon,咱们分配给他一个武器。任务:恐怖分子负责放置炸弹,反恐精英负责拆除炸弹。
为何在这个例子中使用享元模式?由于咱们须要减小玩家对象的数量,因此使用享元模式。若是咱们不使用享元模式,当有n个玩家玩CS,那么咱们须要建立n个对象。如今咱们只须要建立两个对象便可,一个是恐怖分子,一个是反恐精英,咱们能够在须要的时候一次又一次地重用他们。
内部状态:对于两种类型的玩家来讲,任务task是一个内部状态,由于对于全部恐怖分子(或者反恐精英)来讲,任务老是同样的。
外部状态:由于每一个玩家能够携带任何他选择的武器,因此武器weapon是一个外部状态。武器做为参数由客户端传递。
类图以下:
玩家接口:
public interface Player { void assignWeapon(String weapon); }
恐怖分子:
public class Terrorist implements Player{ private final String task; private String weapon; public Terrorist() { task = "放置炸弹"; } @Override public void assignWeapon(String weapon) { this.weapon = weapon; } @Override public String toString() { return "恐怖分子{" + "task='" + task + '\'' + ", weapon='" + weapon + '\'' + '}'; } }
反恐精英:
public class CounterTerrorist implements Player{ private final String task; private String weapon; public CounterTerrorist() { task = "放置炸弹"; } @Override public void assignWeapon(String weapon) { this.weapon = weapon; } @Override public String toString() { return "反恐精英{" + "task='" + task + '\'' + ", weapon='" + weapon + '\'' + '}'; } }
玩家工厂:
public class PlayerFactory { private static HashMap<String, Player> hm = new HashMap<>(); public static Player getPlayer(String playerType) { Player p = null; if (hm.containsKey(playerType)) { p = hm.get(playerType); } else { switch (playerType) { case "Terrorist": p = new Terrorist(); System.out.println("恐怖分子已建立"); break; case "CounterTerrorist": p = new CounterTerrorist(); System.out.println("反恐精英已建立"); break; default: System.out.println("无此玩家类型"); } hm.put(playerType, p); } return p; } }
CS客户端:
public class CounterStrike { private static String[] playerType = {"Terrorist", "CounterTerrorist"}; private static String[] weapon = {"AK-47", "Maverick", "Gut Knife", "Desert Eagle"}; public static void main(String[] args) { for (int i = 0; i < 10; i++) { Player p = PlayerFactory.getPlayer(getPlayerType()); p.assignWeapon(getWeapon()); System.out.println(p.toString()); } } private static String getPlayerType() { Random r = new Random(); int i = r.nextInt(playerType.length); return playerType[i]; } private static String getWeapon() { Random r = new Random(); int i = r.nextInt(weapon.length); return weapon[i]; } }
输出:
恐怖分子已建立 恐怖分子{task='放置炸弹', weapon='Maverick'} 反恐精英已建立 反恐精英{task='放置炸弹', weapon='AK-47'} 恐怖分子{task='放置炸弹', weapon='AK-47'} 反恐精英{task='放置炸弹', weapon='Gut Knife'} 恐怖分子{task='放置炸弹', weapon='Desert Eagle'} 反恐精英{task='放置炸弹', weapon='Desert Eagle'} 恐怖分子{task='放置炸弹', weapon='Gut Knife'} 恐怖分子{task='放置炸弹', weapon='AK-47'} 反恐精英{task='放置炸弹', weapon='Gut Knife'} 反恐精英{task='放置炸弹', weapon='AK-47'}
享元模式使用共享技术有效地支持大量细粒度的对象,减小内存中对象的数量。
享元模式有内部状态和外部状态,内部状态能够共享,外部状态做为参数传入。
2019年5月19日22:13:48