享元模式(FLYWEIGHT),通过运用共享技术有效地支持大量细粒度的对象,由于处理的是细粒度的对象,所以也称为轻量级模式,属于对象结构型模式。享元模式的共享技术是通过区分对象的内部状态和外部状态来实现的。内部状态是对象的内在特征,不会随环境的变化而变化,所以能够共享。外部状态是对象的外在表现,随环境的改变而改变,不可以共享。对于我们经常查看的文章,参考的资料,组成它们的字母或汉字代表的意义是不变的,字母“A"就是字母"A",它不会随所在文章的不同而不同。”A“就是字母A的内部状态。不同的文章,字母所在的位置,样式可能不同,这些会随着文章的变化为变化,所以它们是字母的外部状态。通过将字母对象的内部状态和外部状态区分开来,共享内部状态,在使用字母对象时结合获取到的外部状态就能很好的组成一篇文章,这比直接创建无数个字母对象要节省大量开销。节省开销正是享元模式的价值所在。
一、使用场景
1、当一个应用程序由于使用大量的对象造成很大的存储开销,并且这些对象的大多数状态都可变为外部状态。如果删除对象的外部状态,可以使用相对较少的共享对象来取代很多组对象时可以使用。
2、应用程序不依赖与对象标识。由于Flyweight对象可以被共享,如果应用依赖对象标识,那么进行对象标识测试时即使概念上有明显区别的对象也会返回true,违背原有目的。
二、UML图
注:如UML图所示,享元模式经常和其他模式配合使用,比如使用简单工厂产生享元对象。由于享元对象是共享的,所以只需要维持一份就行,这需要使用单例模式。
三、Java实现
package study.patterns.flyweight; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 享元模式:通过共享机制达到节约内存,提高性能的目的。 * @author qbg */ public class FlyWeightPattern { public static void main(String[] args) { IFlyWeight f1 = FlyWeightFactory.getFlyWeight("f"); IFlyWeight f2 = FlyWeightFactory.getFlyWeight("f"); IFlyWeight f3 = FlyWeightFactory.getFlyWeight("ff"); System.out.println("f1==f2:"+(f1==f2)); System.out.println("f1==f3:"+(f1==f3)); } } /** * 抽象享元接口 */ interface IFlyWeight{ public void operation(OuterState outer); } /** * 享元对象内部状态,比如字母,汉字等 */ class InnerState{ private String innerState; public String getInnerState() { return innerState; } public void setInnerState(String innerState) { this.innerState = innerState; } } /** * 享元对象外部状态,比如位置,大小,颜色等 */ class OuterState{ private String outerState; public String getOuterState() { return outerState; } public void setOuterState(String outerState) { this.outerState = outerState; } } /** * 具体享元类,封装享元对象的内部状态 */ class ConcreteFlyWeight implements IFlyWeight{ private InnerState inner; /** * 根据享元对象的内部状态和外部状态进行业务处理 */ @Override public void operation(OuterState outer) { System.out.println(inner.getInnerState()+":"+outer.getOuterState()); } } /** * 享元对象工厂类,用于产生享元对象,相当于享元对象池 */ class FlyWeightFactory{ private static Map<String,IFlyWeight> flyweights = new ConcurrentHashMap<String,IFlyWeight>(); public static IFlyWeight getFlyWeight(String key){ //key存在则直接返回key对应的对象 if(flyweights.containsKey(key)){ return flyweights.get(key); }else{ //key不存在,则创建对应的flyweight对象,放到享元池中,并返回 IFlyWeight flyweight = new ConcreteFlyWeight(); flyweights.put(key, flyweight); return flyweight; } } }运行结果:
f1==f2:true f1==f3:false从结果可以看出f1和f2指向相同的内存空间,即达到了共享的目的。然后只需将不同的外部状态传给享元对象,则享元对象就可以有不同的表现。
注:java语言中有很多地方用到了享元模式。比如对字符串的处理。
package study.patterns.flyweight; public class StringFlyWeight { public static void main(String args[]) { String str1 = "abcd"; String str2 = "abcd"; String str3 = "ab" + "cd"; String str4 = "ab"; str4 += "cd"; System.out.println(str1 == str2);//str1和str2引用相同的对象,这个对象指向"abcd"的存储空间(享元模式),所以true System.out.println(str1 == str3);//"ab"+"cd"的操作会被编译器优化,所以结果同上 System.out.println(str1 == str4);//str4初始分配的内存空间和str1不同,所以false //由于java中字符串是不可变的,一切针对字符串的修改都是在新创建的对象基础上进行的, //所以对str2的修改促使str2重新指向新的内存空间,故结果为false str2 += "e"; System.out.println(str1 == str2); } }运行结果:
true true false false
四、模式优缺点
优点:
1、可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
2、 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点:
1、享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
2、 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。