一、概述
运用共享技术有效地支持大量细粒度对象的复用。在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象。
在享元模式中存储这些共享实例对象的地方称为享元池,当需要的时候就将对象从享元池中取出,实现对象的复用。
二、结构与实现
- 结构:
(1)Flyweight:
抽象的享元角色。声明了具体享元类的公共方法,这些方法可以向外界提供对象的外部状态和设置对象的内部状态。
内部状态:对象共享出来的信息,不会随环境的改变而改变。
外部状态:对象得以依赖的一个标记,是随环境的改变而改变,不可共享的状态。
(2)ConcreteFlyweight:
具体享元角色。实现Flyweight定义的相关业务;在具体享元类中为内部提供了存储空间,通常结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的对象。一般放在享元工厂中。
(3)UnsharedConcreteFlyweight:
不可共享的享元角色,一般不出现在享元工厂中。
(4)FlyweightFactory:
享元工厂类用于创建并管理享元对象。用于构建一个池容器(集合),同时提供从池中获取对象的方法。一般放置具体的享元对象。
- 实现:
//Flyweight
abstract class Flyweight{
abstract void operation(String extrinsicState);
}
//ConcreteFlyweight
class ConcreteFlyweight extends Flyweight{
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState){
this.intrinicState=intrinsicState;
}
//外部状态extrinsicState在使用时由外部设置,不保存在享元对象中,
//即使是同一个对象,在每一次调用时可以传入不同的外部状态。
public void operation(String extrinsicState){
//定义具体的业务方法
}
}
//UnsharedConcreteFlyweight
class UnsharedConcreteFlyweight extends Flyweight{
public void operation(String extrinsicState)
}
//FlyweightFactory
class FlyweightFactory{
private HashMap flyweights=new HashMap();
public Flyweight getFlyweight(String key){
if(flyweights.containsKey(key)){
return (Flyweight)flyweights.get(key);
}else{
Flyweight fw=new ConcreteFlyweight();
flyweights.put(key,fw);
return fw;
}
}
}
三、应用案例
- 分析:
黑白棋子分别对应为两个具体享元类,抽象享元类定义获得棋子颜色方法,享元工厂类作为享元池,使用单例模式设计。
-
类图
-
实现
abstract class Chess{
public abstract String getColor();
public void display(){
System.out.println("棋子颜色是:"+this.getColor());
}
}
class BlackChess extends Chess{
@Override
public String getColor() {
return "黑色";
}
}
class WhiteChess extends Chess{
@Override
public String getColor() {
return "白色";
}
}
class ChessFactory{
//1.单例模式:静态私有变量,私有构造方法,静态公有工厂方法
//2.定义享元池(集合),在私有构造方法中进行设置
//3.获取池中棋子
private static ChessFactory cfc=new ChessFactory();
private static Hashtable hash;
private ChessFactory(){
hash=new Hashtable();
Chess black=new BlackChess();
Chess white=new WhiteChess();
hash.put("b",black);
hash.put("w",white);
}
public static ChessFactory getInstance(){
return cfc;
}
public static Chess getChess(String color){
return (Chess)hash.get(color);
}
}
//Client
public class client {
public static void main(String[] args) {
ChessFactory cfc=ChessFactory.getInstance();
Chess w1=cfc.getChess("w");
Chess w2=cfc.getChess("w");
w1.display();
w2.display();
System.out.println("两白棋子是否为相同:"+(w1==w2));
System.out.println("======================================");
Chess b1=cfc.getChess("b");
Chess b2=cfc.getChess("b");
b1.display();
b2.display();
System.out.println("两黑棋子是否相同:"+(b1==b2));
}
}
四、扩展
- 有外部状态的享元模式
继续对应用案例进行分析,不难发现虽然黑白棋可以共享,但是它们将显示在棋盘的不同位置,如何让相同的黑棋或白棋能够多次重复显示且位于一个棋盘的不同地方?
解决方法:将棋子的位置定义为棋子的一个外部状态,在需要时再进行设置。
//增加坐标类
class Coordinates {
private int x;
private int y;
public Coordinates(int x, int y) {
this.x = x;
this.y = y;
}
//getter和setter方法省略
}
//修改Chess抽象类的display方法
public void display(Coordinates coord){
System.out.println("棋子颜色是:"+this.getColor()+",棋子所在位置:("+coord.getX()+","+coord.getY()+")");
}
//客户端在调用display方法是传入外部状态(位置坐标)。
b1.display(new Coordinates(1,4));
- 单纯享元模式和复合享元模式
(1)单纯享元模式:所有的具体享元类都是可以共享的,不存在非共享具体享元类。
(2)复合享元模式:将单纯享元对象使用组合模式加以组合还可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者可以分享。如果希望多个内部状态不同的享元对象设置相同的外部状态,可以考虑使用复合享元模式。
- 享元模式与String类
String s1="ab";
String s2="ab";
当执行String s1="ab"时,会创建一个新的字符串对象,而当下一次再次创建相同内容的字符串时,会将它的引用指向ab,不会重新分配内存空间,从而实现了ab在内存空间中的共享。
五、总结
- 内部状态和外部状态的概念。
- 享元模式的角色:抽象享元类,具体享元类,工厂享元类(享元池,里面存有抽象享元类的集合)。
- 适用情况:系统中大量相同或相似的对象,造成内存的大量耗费;对象的大部分都可以外部化,可以将这些外部状态传入到对象中;需要维护一个存储享元对象的享元池,因此应当在需要多次重复使用享元对象时才进行使用。