设计模式12---享元模式(Flyweight Pattern)
享元模式
定义:共享元对象,运用共享技术有效地支持大量细粒度对象的复用。如果在一个系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必为每一次使用创建新的对象。
享元模式是为数不多的、只为提升系统性能而生的设计模式,主要作用就是复用大对象(重量级对象),以节省内存空间和对象创建时间。
面向对象可以非常方便的解决一些扩展性的问题,但是在这个过程中系统务必会产生一些类或者对象,如果系统中存在对象的个数过多时,将会导致系统的性能下降。对于这样的问题解决最简单直接的办法就是减少系统中对象的个数。享元模式提供了一种解决方案,使用共享技术实现相同或者相似对象的重用。也就是说实现相同或者相似对象的代码共享。
所谓享元模式就是运行共享技术有效地支持大量细粒度对象的复用。系统使用少量对象,而且这些都比较相似,状态变化小,可以实现对象的多次复用。
共享模式是支持大量细粒度对象的复用,所以享元模式要求能够共享的对象必须是细粒度对象。
首先了解两个概念:内部状态、外部状态。
内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。
由于享元模式区分了内部状态和外部状态,所以我们可以通过设置不同的外部状态使得相同的对象可以具备一些不同的特性,而内部状态设置为相同部分。在我们的程序设计过程中,我们可能会需要大量的细粒度对象来表示对象,如果这些对象除了几个参数不同外其他部分都相同,这个时候我们就可以利用享元模式来大大减少应用程序当中的对象。如何利用享元模式呢?这里我们只需要将他们少部分的不同的部分当做参数移动到类实例的外部,然后在方法调用的时候将他们传递过来就可以了。这里也就说明了一点:内部状态存储于享元对象内部,而外部状态则应该由客户端来考虑。
二、 模式结构
下图是享元模式的UML结构图
享元模式存在如下几个角色:
模式结构
-
Flyweight: 享元接口,所有具体享元类的超类或接口,通过该接口Flyweight可以接受并作用于外部状态。通过该接口可以传入外部的状态,在享元对象的方法处理中可能会使用这些外部的数据。
-
ConcreteFlyweight: 具体的享元实现对象,指定内部状态,必须是共享的,需要封装Flyweight的内部状态。
-
UnshareConcreteFlyweight: 非共享的享元实现对象,并不是所有的Flyweight实现对象都需要共享。非共享的享元实现对象通常是对享元对象的组合对象。
-
FlyweightFactoty: 享元工厂类,主要用来创建并管理共享的享元对象,并对外提供访问共享享元的接口。当用户请求一个Flyweight时,FlyweightFactory就会提供一个已经创建的Flyweight对象或者新建一个(如果不存在)。
-
Client: 享元客户端,主要的工作就是维持一个对Flyweight的引用,计算或存储享元的外部状态,当然这里可访问共享和不共享的Flyweight对象。
享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
三、 模式实现
1、单纯享元模式:在单纯享元模式中,所有的享元对象都是可以共享的,即所有抽象享元类的子类都可共享,不存在非共享具体享元类。
1 //抽象享元角色类 2 public interface Flyweight { 3 //一个示意性方法,参数state是外蕴状态 4 public void operation(String state); 5 } 6 7 //具体享元角色类 8 //具体享元角色类ConcreteFlyweight有一个内蕴状态,在本例中一个Character类型的intrinsicState属性代表,它的值应当在享元对象 9 //被创建时赋予。所有的内蕴状态在对象创建之后,就不会再改变了。如果一个享元对象有外蕴状态的话,所有的外部状态都必须存储在客户端, 10 //在使用享元对象时,再由客户端传入享元对象。这里只有一个外蕴状态,operation()方法的参数state就是由外部传入的外蕴状态。 11 public class ConcreteFlyweight implements Flyweight { 12 private Character intrinsicState = null; 13 /** 14 * 构造函数,内蕴状态作为参数传入 15 * @param state 16 */ 17 public ConcreteFlyweight(Character state){ 18 this.intrinsicState = state; 19 } 20 21 22 /** 23 * 外蕴状态作为参数传入方法中,改变方法的行为, 24 * 但是并不改变对象的内蕴状态。 25 */ 26 @Override 27 public void operation(String state) { 28 // TODO Auto-generated method stub 29 System.out.println("Intrinsic State = " + this.intrinsicState); 30 System.out.println("Extrinsic State = " + state); 31 } 32 33 } 34 35 36 //享元工厂角色类 37 //享元工厂角色类,必须指出的是,客户端不可以直接将具体享元类实例化,而必须通过一个工厂对象,利用一个factory()方法得到享元对象。 38 //一般而言,享元工厂对象在整个系统中只有一个,因此也可以使用单例模式。 39 40 //当客户端需要单纯享元对象的时候,需要调用享元工厂的factory()方法,并传入所需的单纯享元对象的内蕴状态,由工厂方法产生所需要的 41 //享元对象。 42 public class FlyweightFactory { 43 private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>(); 44 45 public Flyweight factory(Character state){ 46 //先从缓存中查找对象 47 Flyweight fly = files.get(state); 48 if(fly == null){ 49 //如果对象不存在则创建一个新的Flyweight对象 50 fly = new ConcreteFlyweight(state); 51 //把这个新的Flyweight对象添加到缓存中 52 files.put(state, fly); 53 } 54 return fly; 55 } 56 } 57 58 59 //客户端类 60 public class Client { 61 62 public static void main(String[] args) { 63 // TODO Auto-generated method stub 64 FlyweightFactory factory = new FlyweightFactory(); 65 Flyweight fly = factory.factory(new Character('a')); 66 fly.operation("First Call"); 67 68 fly = factory.factory(new Character('b')); 69 fly.operation("Second Call"); 70 71 fly = factory.factory(new Character('a')); 72 fly.operation("Third Call"); 73 } 74 75 }
虽然客户端申请了三个享元对象,但是实际创建的享元对象只有两个,这就是共享的含义。运行结果如下:
2、复合享元模式:将一些单纯享元使用组合模式加以组合,可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
实现示例:
1 //抽象享元角色类 2 public interface Flyweight { 3 //一个示意性方法,参数state是外蕴状态 4 public void operation(String state); 5 } 6 7 8 //具体享元角色类 9 //具体享元角色类ConcreteFlyweight有一个内蕴状态,在本例中一个Character类型的intrinsicState属性代表,它的值应当在享元对象 10 //被创建时赋予。所有的内蕴状态在对象创建之后,就不会再改变了。如果一个享元对象有外蕴状态的话,所有的外部状态都必须存储在客户端, 11 //在使用享元对象时,再由客户端传入享元对象。这里只有一个外蕴状态,operation()方法的参数state就是由外部传入的外蕴状态。 12 public class ConcreteFlyweight implements Flyweight { 13 private Character intrinsicState = null; 14 /** 15 * 构造函数,内蕴状态作为参数传入 16 * @param state 17 */ 18 public ConcreteFlyweight(Character state){ 19 this.intrinsicState = state; 20 } 21 22 23 /** 24 * 外蕴状态作为参数传入方法中,改变方法的行为, 25 * 但是并不改变对象的内蕴状态。 26 */ 27 @Override 28 public void operation(String state) { 29 // TODO Auto-generated method stub 30 System.out.println("Intrinsic State = " + this.intrinsicState); 31 System.out.println("Extrinsic State = " + state); 32 } 33 34 } 35 36 37 //复合享元角色类 38 //复合享元对象是由单纯享元对象通过复合而成的,因此它提供了add()这样的聚集管理方法。由于一个复合享元对象具有不同的聚集元素, 39 //这些聚集元素在复合享元对象被创建之后加入,这本身就意味着复合享元对象的状态是会改变的,因此复合享元对象是不能共享的。 40 //复合享元角色实现了抽象享元角色所规定的接口,也就是operation()方法,这个方法有一个参数,代表复合享元对象的外蕴状态。 41 //一个复合享元对象的所有单纯享元对象元素的外蕴状态都是与复合享元对象的外蕴状态相等的; 42 //而一个复合享元对象所含有的单纯享元对象的内蕴状态一般是不相等的,不然就没有使用价值了。 43 public class ConcreteCompositeFlyweight implements Flyweight { 44 45 private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>(); 46 /** 47 * 增加一个新的单纯享元对象到聚集中 48 */ 49 public void add(Character key , Flyweight fly){ 50 files.put(key,fly); 51 } 52 /** 53 * 外蕴状态作为参数传入到方法中 54 */ 55 @Override 56 public void operation(String state) { 57 Flyweight fly = null; 58 for(Object o : files.keySet()){ 59 fly = files.get(o); 60 fly.operation(state); 61 } 62 63 } 64 65 } 66 67 68 //享元工厂角色类 69 //享元工厂角色提供两种不同的方法,一种用于提供单纯享元对象,另一种用于提供复合享元对象。 70 public class FlyweightFactory { 71 private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>(); 72 /** 73 * 复合享元工厂方法 74 */ 75 public Flyweight factory(List<Character> compositeState){ 76 ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight(); 77 78 for(Character state : compositeState){ 79 compositeFly.add(state,this.factory(state)); 80 } 81 82 return compositeFly; 83 } 84 /** 85 * 单纯享元工厂方法 86 */ 87 public Flyweight factory(Character state){ 88 //先从缓存中查找对象 89 Flyweight fly = files.get(state); 90 if(fly == null){ 91 //如果对象不存在则创建一个新的Flyweight对象 92 fly = new ConcreteFlyweight(state); 93 //把这个新的Flyweight对象添加到缓存中 94 files.put(state, fly); 95 } 96 return fly; 97 } 98 } 99 100 101 //客户端类 102 public class Client { 103 104 public static void main(String[] args) { 105 List<Character> compositeState = new ArrayList<Character>(); 106 compositeState.add('a'); 107 compositeState.add('b'); 108 compositeState.add('c'); 109 compositeState.add('a'); 110 compositeState.add('b'); 111 112 FlyweightFactory flyFactory = new FlyweightFactory(); 113 Flyweight compositeFly1 = flyFactory.factory(compositeState); 114 Flyweight compositeFly2 = flyFactory.factory(compositeState); 115 compositeFly1.operation("Composite Call"); 116 117 System.out.println("---------------------------------"); 118 System.out.println("复合享元模式是否可以共享对象:" + (compositeFly1 == compositeFly2)); 119 120 Character state = 'a'; 121 Flyweight fly1 = flyFactory.factory(state); 122 Flyweight fly2 = flyFactory.factory(state); 123 System.out.println("单纯享元模式是否可以共享对象:" + (fly1 == fly2)); 124 } 125 }
运行结果如下:
四、 模式优缺点
享元模式是一个 考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能。
优点
1、享元模式的优点在于它能够极大的减少系统中对象的个数。
2、享元模式由于使用了外部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。
缺点
1、由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。
2、为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
五、 模式适用场景
JDK 里的享元模式:在 JDK 的设计里,也有很享元模式,如一些常量池的设计(String 常量池、Integer 常量池等等);
1、如果一个系统中存在大量的相同或者相似的对象,由于这类对象的大量使用,会造成系统内存的耗费,可以使用享元模式来减少系统中对象的数量。
2、对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
六、 模式总结
1、享元模式可以极大地减少系统中对象的数量。但是它可能会引起系统的逻辑更加复杂化。
2、享元模式的核心在于享元工厂,它主要用来确保合理地共享享元对象。
3、内部状态为不变共享部分,存储于享元享元对象内部,而外部状态是可变部分,它应当油客户端来负责。