引言
按照以前的惯例,先放出上一节讲的组合模式,这节我们来说说享元模式。
示例地址
Demo
类图
定义
使用共享对象可有效地支持大量的细粒度的对象。
使用场景
1. 系统中存在大量的相似对象。
2. 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。
3. 需要缓冲池的场景。
享元模式的各种角色
1. Flyweight:享元对象接口
2. ConcreteFlyweight:具体的享元对象
3. FlyweightFactory:享元工厂,负责管理享元对象池和创建享元对象
享元模式中的概念
享元对象能做到共享的关键是区分内部状态(Internal State)和外部状态(External State)。其中:
1. 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享。
2. 外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。
单纯享元模式
单纯享元模式中,所有的具体享元类都是可以共享的,不存在非共享具体享元类。
1. 享元角色接口
/**
* 享元角色接口
*
* @author [email protected]
* @created 2018/8/6 下午3:49.
*/
public interface Flyweight {
//外部状态
public void operation(String state);
}
2. 享元角色的实现类
/**
* 享元角色的实现类
*
* @author [email protected]
* @created 2018/8/6 下午3:50.
*/
public class ConcreteFlyweight implements Flyweight {
private String intrinsicState = null;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String state) {
// 内部状态
System.out.println("Intrinsic State = " + this.intrinsicState);
// 外部状态
System.out.println("Extrinsic State = " + state);
}
}
3. 享元角色工厂
/**
* 享元角色工厂
*
* @author [email protected]
* @created 2018/8/6 下午3:54.
*/
public class FlyweighttFactory {
private Map mFlyweightMap = new HashMap<>();
public Flyweight factory(String state) {
//先从已有的缓存列表中查询对象是否已存在
Flyweight flyweight = mFlyweightMap.get(state);
if (flyweight == null) {
//如果对象不存在,则重新创建一个新的Flyweight对象
flyweight = new ConcreteFlyweight(state);
//将新生成的对象放入缓存列表中
mFlyweightMap.put(state, flyweight);
}
//返回对象
return flyweight;
}
}
4. Client
FlyweighttFactory factory=new FlyweighttFactory();
Flyweight flyweightA = factory.factory("A");
flyweightA.operation("First Call , A State");
System.out.println(flyweightA);
Flyweight flyweightB = factory.factory("B");
flyweightA.operation("Second Call , B State");
System.out.println(flyweightB);
Flyweight flyweightC = factory.factory("C");
flyweightA.operation("Third Call , C State");
System.out.println(flyweightC);
复合享元模式
将一些单纯享元对象使用组合模式加以组合,还可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
通过复合享元模式,可以确保复合享元类CompositeConcreteFlyweight中所包含的每个单纯享元类ConcreteFlyweight都具有相同的外部状态,而这些单纯享元的内部状态往往可以不同。如果希望为多个内部状态不同的享元对象设置相同的外部状态,可以考虑使用复合享元模式。
1. 享元角色接口
/**
* 享元角色接口
*
* @author [email protected]
* @created 2018/8/6 下午3:49.
*/
public interface Flyweight {
//外部状态
public void operation(String state);
}
2. 享元角色的实现类
/**
* 享元角色的实现类
*
* @author [email protected]
* @created 2018/8/6 下午3:50.
*/
public class ConcreteFlyweight implements Flyweight {
private String intrinsicState = null;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String state) {
// 内部状态
System.out.println("Intrinsic State = " + this.intrinsicState);
// 外部状态
System.out.println("Extrinsic State = " + state);
}
}
3. 复合享元角色类
/**
* 复合享元角色类
*
* @author [email protected]
* @created 2018/8/6 下午4:54.
*/
public class ConcreteCompositeFlyweight implements Flyweight {
private Map mStringFlyweightMap = new HashMap<>();
public void add(String key, Flyweight flyweight) {
mStringFlyweightMap.put(key, flyweight);
}
@Override
public void operation(String state) {
Flyweight flyweight = null;
for (Object o : mStringFlyweightMap.keySet()) {
flyweight = mStringFlyweightMap.get(o);
flyweight.operation(state);
System.out.println(flyweight);
}
}
}
4. 享元模式工厂
/**
* 享元模式工厂
*
* @author [email protected]
* @created 2018/8/6 下午5:34.
*/
public class FlyweightFactory {
private Map mStringFlyweightMap = new HashMap();
/**
* 复合享元工厂方法
*
* @param compositeState
* @return
*/
public Flyweight factory(List compositeState) {
ConcreteCompositeFlyweight concreteCompositeFlyweight = new ConcreteCompositeFlyweight();
for (String state : compositeState) {
concreteCompositeFlyweight.add(state, this.factory(state));
}
return concreteCompositeFlyweight;
}
/**
* 单纯享元工厂方法
*
* @param state
* @return
*/
public Flyweight factory(String state) {
//先从已有的缓存列表中查询对象是否已存在
Flyweight flyweight = mStringFlyweightMap.get(state);
if (flyweight == null) {
//如果对象不存在,则重新创建一个新的Flyweight对象
flyweight = new ConcreteFlyweight(state);
//将新生成的对象放入缓存列表中
mStringFlyweightMap.put(state, flyweight);
}
//返回对象
return flyweight;
}
}
5. Client
List compositeState = new ArrayList();
compositeState.add("A");
compositeState.add("B");
compositeState.add("C");
compositeState.add("B");
compositeState.add("A");
FlyweightFactory flyweightFactory = new FlyweightFactory();
Flyweight compositeFly1 = flyweightFactory.factory(compositeState);
Flyweight compositeFly2 = flyweightFactory.factory(compositeState);
compositeFly1.operation("Composite1 Call");
compositeFly2.operation("Composite2 Call");
System.out.println("---------------------------------------------");
System.out.println("复合享元模式是否可以共享对象:" + (compositeFly1 == compositeFly2));
System.out.println(compositeFly1);
System.out.println(compositeFly2);
享元模式实例应用
上高中的时候,我们玩个一个游戏,叫做“五子棋”。里面有黑白2种棋子。如果我们按照一般的写法,有100个棋子我们需要创建100个对象,那么10000个呢,内存估计直接OOM了。下面我们使用享元模式来解决问题。
1. 棋子接口
/**
* 棋子接口
*
* @author [email protected]
* @created 2018/8/7 下午3:55.
*
*/
public abstract class QiZi {
public abstract String getColor();
public void display(Coordinates coordinates) {
System.out.println("棋子颜色:" + this.getColor() + ",棋子位置:" + coordinates.getX() + "," + coordinates.getY() );
}
}
2. 黑子
/**
* 黑色的棋子
*
* @author [email protected]
* @created 2018/8/7 下午3:58.
*
*/
public class BlackQiZi extends QiZi {
@Override
public String getColor() {
return "黑色";
}
}
3. 白子
/**
* 白色的棋子
*
* @author [email protected]
* @created 2018/8/7 下午3:58.
*
*/
public class WhiteQiZi extends QiZi{
@Override
public String getColor() {
return "白色";
}
}
4. 外部状态 坐标
/**
* 外部状态
*
* @author [email protected]
* @created 2018/8/7 下午4:26.
*
*/
public class Coordinates {
private int x;
private int y;
public Coordinates(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
5. 棋子工厂
/**
* 生产棋子的工厂
*
* @author [email protected]
* @created 2018/8/7 下午4:02.
*
*/
public class QiZiFactory {
private static QiZiFactory instance = new QiZiFactory();
private static Hashtable ht; //使用Hashtable来存储享元对象,充当享元池
private QiZi black,white;
private QiZiFactory() {
ht = new Hashtable();
black = new BlackQiZi();
ht.put("b",black);
white = new WhiteQiZi();
ht.put("w",white);
}
//返回享元工厂类的唯一实例
public static QiZiFactory getInstance() {
return instance;
}
//通过key来获取存储在Hashtable中的享元对象
public static QiZi getIgoChessman(String color) {
return (QiZi)ht.get(color);
}
}
6. Client
QiZi black1,black2,white1,white2;
QiZiFactory factory = QiZiFactory.getInstance();
//通过享元工厂获取三颗黑子
black1 = factory.getIgoChessman("b");
black2 = factory.getIgoChessman("b");
System.out.println("判断两颗黑子是否相同:" + (black1==black2));
//通过享元工厂获取两颗白子
white1 = factory.getIgoChessman("w");
white2 = factory.getIgoChessman("w");
System.out.println("判断两颗白子是否相同:" + (white1==white2));
//显示棋子,同时设置棋子的坐标位置
black1.display(new Coordinates(1,2));
black2.display(new Coordinates(3,4));
white1.display(new Coordinates(2,5));
white2.display(new Coordinates(2,4));
享元模式优缺点
优点
1. 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
2. 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点
1. 享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
2. 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。