享元模式(Flyweight):享,就是共享;元,就是元数据。简单来说,这个模式就是将系统中的对象公用,不用创建很多对象造成JVM拥塞。类似于平常用到的数据库连接池。
把一个对象的状态分成两部分,变与不变。通过共享不变的部分,变化的状态通过参数传入,达到减少对象数量的目的。享元模式通常结合工厂模式一起实现,具体怎么做呢?最常见的例子就是Java中的字符串常量。下面来看个其他实例。
还是手机的例子,几乎每个手机都有个通讯录,上面记录了我们的家人、同学、朋友的联系方式。每条记录都有其变化的地方和相对不变的地方。例如,我们可以对通讯录进行分组,家人组、同学组等。一般情况下,这个关系是不会发生变化的,而每条记录上的人以及每个人的联系电话和住址等,却可能是随着时间的变化而变化,这部分就是变化的。手机的内存就那么多,能省点就省省,下面我们以享元模式设计下这个通讯录的存储。
首先来个抽象享元类:
1 package org.scott.flyweight; 2 /** 3 * @author Scott 4 * @date 2013年12月17日 5 * @description 6 */ 7 public abstract class Address { 8 public abstract String getType(); 9 10 public void display(UserInfo user){ 11 System.out.println("\nName is " + user.getName() + ", phone number is " + user.getPhoneNumber() + ", and type is " + getType()); 12 } 13 }
这里,我们将每一条通讯录拆分为不变的状态和变化的状态,联系人的组别是不变的信息,而联系人的通讯信息比如手机号等属于变化的信息。此处将变化的信息重新封装成一个UserInfo类,作为外部状态传入,而不变的分组信息作为内部状态通过方法直接获取。下面是外部状态用户基本信息类:
1 package org.scott.flyweight; 2 3 /** 4 * @author Scott 5 * @date 2013年12月20日 6 * @description 7 */ 8 public class UserInfo { 9 private String name; 10 private String phoneNumber; 11 12 public UserInfo(String name, String phoneNumber){ 13 this.name = name; 14 this.phoneNumber = phoneNumber; 15 } 16 17 public String getName() { 18 return name; 19 } 20 21 public String getPhoneNumber() { 22 return phoneNumber; 23 } 24 }
然后是具体的享元类。
同学分组:
1 package org.scott.flyweight; 2 /** 3 * @author Scott 4 * @date 2013年12月17日 5 * @description 6 */ 7 public class Classmate extends Address { 8 9 @Override 10 public String getType() { 11 return "Classmate"; 12 } 13 }
家庭分组:
1 package org.scott.flyweight; 2 /** 3 * @author Scott 4 * @date 2013年12月20日 5 * @description 6 */ 7 public class Relative extends Address { 8 9 @Override 10 public String getType() { 11 return "Relative"; 12 } 13 14 }
此处只设置了两个分组,组别的信息也很少,就一个名字,当然想复杂点也可以对分组做不同的颜色显示、不同的来电铃声、信息铃声等等,up to you~
自然,既然是共享元数据,就不能随便创建这个对象,于是下面是我们的享元工厂类:
1 package org.scott.flyweight; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 /** 7 * @author Scott 8 * @date 2013年12月17日 9 * @description 10 */ 11 public class AddressFactory { 12 private static Map<String, Address> pool = new HashMap<String, Address>(); 13 private static AddressFactory factory = new AddressFactory(); 14 15 private AddressFactory(){ 16 Address classmate, relative; 17 classmate = new Classmate(); 18 relative = new Relative(); 19 pool.put("C", classmate); 20 pool.put("R", relative); 21 } 22 23 public static AddressFactory getInstance(){ 24 return factory; 25 } 26 27 public Address getAddress(String type){ 28 return pool.get(type); 29 } 30 }
结合了一个单例模式来创建工厂,工厂内部维护了一个HashMap用于存储联系人分组类型也就是享元对象,作为一个享元池。
客户端也很重要,来看看客户端怎么“享元”的:
1 package org.scott.flyweight; 2 /** 3 * @author Scott 4 * @date 2013年12月17日 5 * @description 6 */ 7 public class FlyWeightClient { 8 9 public static void main(String[] args) { 10 Address adrs1, adrs2, adrs3, adrs4, adrs5, adrs6; 11 AddressFactory factory; 12 13 factory = AddressFactory.getInstance(); 14 15 adrs1 = factory.getAddress("C"); 16 adrs2 = factory.getAddress("C"); 17 adrs3 = factory.getAddress("C"); 18 adrs4 = factory.getAddress("C"); 19 adrs5 = factory.getAddress("R"); 20 adrs6 = factory.getAddress("R"); 21 22 adrs1.display(new UserInfo("老大", "5455221")); 23 adrs2.display(new UserInfo("二统", "8786653")); 24 adrs3.display(new UserInfo("小五", "3212224")); 25 adrs4.display(new UserInfo("老于", "8712126")); 26 System.out.println("\n-----------------------------"); 27 adrs5.display(new UserInfo("堂哥", "1323545")); 28 adrs6.display(new UserInfo("表弟", "1325433")); 29 } 30 31 }
客户端中,分组类型虽然创建了多个,因为每个联系人都有自己的分组,但是相同的分组对象都是指向的同一个内存地址,这是享元对象的内部不变的状态,外部状态时通过创建了一个UserInfo对象来传入的。
运行结果:
Name is 老大, phone number is 5455221, and type is Classmate Name is 二统, phone number is 8786653, and type is Classmate Name is 小五, phone number is 3212224, and type is Classmate Name is 老于, phone number is 8712126, and type is Classmate ----------------------------- Name is 堂哥, phone number is 1323545, and type is Relative Name is 表弟, phone number is 1325433, and type is Relative
个人感觉享元模式用起来比较复杂,因为需要对我们掌握的信息进行深度分析,找出哪些信息可以作为内部状态,哪些可以作为外部状态,然后才把信息整合起来。但是如果用好了享元模式,可以减少很多的资源使用。
现在手机上的五子棋游戏、围棋游戏,都是黑白子,或者黑子,或者是白字,变化的是位置,把黑白子作为内部不变的状态,或黑或白。把位置抽象出来作为外部状态传入。这样的话就不必每个操作都得创建个棋子对象,这倒也是个不错的应用范例。