设计模式之享元模式
定义
运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
核心
享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区分了内部状态(Intrinsic State)和外部状态(Extrinsic State)。
(1)内部状态:可以共享,不会随环境变化而改变。因此,一个享元可以具有内部状态并可以共享。
(2)外部状态:不可以共享,会随环境变化而改变。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外部状态不可以影响享元对象的内部状态,它们是相互独立的。
(3)享元模式可以分成单纯享元模式和复合享元模式两种形式。
推荐地址: http://mp.weixin.qq.com/s/Zw8-XCmDit9IkvCjUy9RzQ
正因为区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。
享元模式分类
(1)FlyweightFactory享元工厂类
享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。
(2)FlyWeight抽象享元类
通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
(3)ConcreteFlyWeight具体享元类
它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
(4)UnsharedConcreteFlyWeight非共享享元类(非共享具体享元类)
并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
类图
代码如下
例子:围棋软件设计
抽象享元类
/**
* 抽象享元类
* @author mama
*
*/
public interface ChessFlyWeight {
void setColor(String c);
String getColor();
void display(Coordinate c);
}
非共享享元类(非共享具体享元类)
/**
* 非共享具体享元类(外部状态)
* @author mama
*
*/
public class Coordinate {
private int x,y;
public Coordinate(int x, int y) {
super();
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;
}
}
具体享元类
/**
* 具体享元类
* @author mama
*
*/
public class ConcreteChess implements ChessFlyWeight{
private String color;
public ConcreteChess(String color) {
super();
this.color = color;
}
@Override
public void setColor(String c) {
this.color=c;
}
@Override
public String getColor() {
return color;
}
@Override
public void display(Coordinate c) {
System.out.println("棋子颜色:"+color);
System.out.println("棋子位置:"+c.getX()+"----"+c.getY());
}
}
享元工厂类
/**
* 享元工厂类
* @author mama
*
*/
public class ChessFlyWeightFactory {
//享元池
private static Map<String,ChessFlyWeight> map= new HashMap<String, ChessFlyWeight>();
public static ChessFlyWeight getChess(String color) {
if(map.get(color)!=null) {
return map.get(color);
}else {
ChessFlyWeight cfw=new ConcreteChess(color);
map.put(color, cfw);
return cfw;
}
}
}
测试
public class Client {
public static void main(String[] args) {
ChessFlyWeight chess1 = ChessFlyWeightFactory.getChess("黑色");
ChessFlyWeight chess2 = ChessFlyWeightFactory.getChess("黑色");
System.out.println(chess1);
System.out.println(chess2);
chess1.display(new Coordinate(10, 10));
chess1.display(new Coordinate(20, 10));
}
}
结果
优点
(1)极大减少内存中对象的数量
(2)相同或相似对象内存中只存在一份,极大的节约资源,提高系统性能
(3)外部状态相对独立,不影响内部状态
缺点
(1)模式较复杂,使程序逻辑复杂化
(2)为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态使运行时间变长。用时间换取了空间。
开发场景中应用场景
(1)享元模式由于其共享的特性,可以在任何“池”中操作,比如:线程池,数据库连接池
(2)String类的设计也是享元模式
适用场景
内存属于稀缺资源,不要随便浪费。如果有很多个完全相同或相似的对象,我们可以通过享元模式,节省内存。