文章目录
概念
享元模式:运用共享技术有效地支持大量细粒度对象的复用。
当系统中出现大量相同或者相似的对象时,可以考虑使用享元模式。享元模式中提供了一个享元池用于存储已经创建好的对象。享元对象能做到共享的关键是区分了内部状态和外部状态。
内部状态:内部状态是存储在享元对象内部并且不会随环境改变而改变。
外部状态:外部状态是随环境改变而改变的、不可共享的状态。
举个例子,比如五子棋的大小形状就是内部状态 ,它不会随着环境而改变,而五子棋的位置就是外部状态。
结构
享元,就是共享元素的意思。但是英文名字叫做FlyWeight,翻译成中文也不叫享元,直接翻译大概意思是轻量级的意思。
FlyWeight(抽象享元类) :声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部状态,也可以通过这些方法设置外部状态。
ConcreteFlyweight(具体享元类) :具体享元类实现了抽象享元类,在具体享元类中为内部状态提供了存储空间。
UnsharedConcreteFlyweight(非共享具体享元类) :不能共享的子类可设计为非共享具体享元类,当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
FlyweightFactory(享元工厂类):用于创建并管理享元对象,将各种类型的具体享元对象存储在一个享元池中。当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在),返回新创建的实例并将其存储在享元池中。
实例
现在要开发一个围棋软件。棋盘中有大量的黑子和白子,它们的形状、大小一样,只是位置不同。现在用享元模式来完成这个demo。

IgoChessman 类,抽象享元类。
            
            
              java
              
              
            
          
          public abstract class IgoChessman {
    public abstract String getColor();
    public void display(){
        System.out.println("棋子颜色:" + this.getColor());
    }
}
        黑色围棋类。
            
            
              java
              
              
            
          
          public class BlackIgoChessman extends IgoChessman{
    @Override
    public String getColor() {
        return "黑色";
    }
}
        黑色围棋类。
            
            
              java
              
              
            
          
          public class WhiteIgoChessman extends IgoChessman {
    @Override
    public String getColor() {
        return "白色";
    }
}
        围棋棋子工厂类。
            
            
              java
              
              
            
          
          public class IgoChessmanFactory {
   //饿汉式单例类
    private static IgoChessmanFactory instance = new IgoChessmanFactory();
    private static Hashtable ht;
    private IgoChessmanFactory() {
        ht = new Hashtable();
        IgoChessman black, white;
        black = new BlackIgoChessman();
        ht.put("b", black);
        white = new WhiteIgoChessman();
        ht.put("w", white);
    }
    //返回唯一实例
    public static IgoChessmanFactory getInstance() {
        return instance;
    }
    //获取享元对象
    public static IgoChessman getIgoChessman(String color) {
        return (IgoChessman) ht.get(color);
    }
}
        客户端
            
            
              java
              
              
            
          
          public class Client {
    public static void main(String[] args) {
        IgoChessman black1,black2,black3,white1,white2;
        IgoChessmanFactory factory;
        //获取享元工厂对象
        factory = IgoChessmanFactory.getInstance();
        //通过享元工厂获取三颗黑子
        black1 = factory.getIgoChessman("b");
        black2 = factory.getIgoChessman("b");
        black3 = factory.getIgoChessman("b");
        System.out.println("判断两颗黑子是否相同:" + (black1==black2));
        //通过享元工厂获取两颗白子
        white1 = factory.getIgoChessman("w");
        white2 = factory.getIgoChessman("w");
        System.out.println("判断两颗白子是否相同:" + (white1==white2));
        //显示棋子
        black1.display();
        black2.display();
        black3.display();
        white1.display();
        white2.display();
    }
}
        打印结果:

上面是没有外部状态的享元模式,如果有外部状态,如围棋需要加一个坐标类。
坐标类代码
            
            
              java
              
              
            
          
          public class Coordinates {
    private int x;
    private int y;
    
    public Coordinates(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public Coordinates() {
    }
    
    public int getX() {
        return x;
    }
    
    public Coordinates setX(int x) {
        this.x = x;
        return this;
    }
    
    public int getY() {
        return y;
    }
    
    public Coordinates setY(int y) {
        this.y = y;
        return this;
    }
}
        然后再修改一下IgoChessman,依赖坐标对象
            
            
              java
              
              
            
          
          public abstract class IgoChessman {
    public abstract String getColor();
    public void display(Coordinates coordinates){
        System.out.println("棋子颜色:" + this.getColor() + ",棋子位置:" + coordinates.getX() + "," + coordinates.getY());
    }
}
        客户端代码修改
            
            
              java
              
              
            
          
          public class Client {
    public static void main(String[] args) {
        IgoChessman black1,black2,black3,white1,white2;
        IgoChessmanFactory factory;
        //获取享元工厂对象
        factory = IgoChessmanFactory.getInstance();
        //通过享元工厂获取三颗黑子
        black1 = factory.getIgoChessman("b");
        black2 = factory.getIgoChessman("b");
        black3 = 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));
        black3.display(new Coordinates(1,3));
        white1.display(new Coordinates(2,5));
        white2.display(new Coordinates(2,4));
    }
}
        打印结果:

总结
当需要创建大量重复对象的时候,可以考虑使用享元模式。java中的String类,也是用了享元模式。当我们创建一个字符串,如果这个字符串对象第一次创建的话,就会放到享元池中,下次再创建内容相同的字符串对象,新创建的对象就会将它的引用指向第一次创建的对象,不会分配新的内存,以此来实现字符串的共享。
            
            
              java
              
              
            
          
              String str1 = "abcd";
        String str2 = "abcd";
        String str3 = "ab" + "cd";
        String str4 = "ab";
        str4 += "cd";
        System.out.println(str1 == str2);
        System.out.println(str1 == str3);
        System.out.println(str1 == str4);
        打印结果:

第三个为false,是因为一开始str4是一个对象,和str1的初始值不同,所以这两个的地址是不一样的。