享元模式(结构型)

思考问题:设计一个围棋游戏,模拟一个下棋动作,如何设计?
解答:很直接的,我们会设计一个棋盘类Chessboard,一个棋子类Chesspiece,每下一枚棋子时就new一个棋子对象(传入颜色、位置),然后将这些棋子装入到一个容器中。

这种简单粗暴的方式确实是解决了问题,但你会发现,棋子永远只有黑白色,棋子对象的函数都是一样的, 主要的变化只是(x,y)位置而已,那能不能单独把(x,y)独立出来让调用者管理(而非记录到棋子内部),让棋子对象变成一个可重用的对象呢?这样就可以避免产生大量棋子对象了,这就是享元模式(把棋子享元的无法共享的状态交给调用者管理,模式只管理(重用)棋子享元)。

享元模式(结构型)_第1张图片
享元接口角色:该角色对享元类进行抽象,需要外部状态的操作可以通过参数的形式传入享元对象。
具体享元对象角色:该角色实现享元接口定义的业务,注意享元对象的内部状态与环境无关,从而使得享元对象可以在系统内共享。
享元工厂角色:该角色就是构造一个池容器(pool),负责创建和管理享元角色,并提供从池容器中获得对象的方法,保证享元角色会检查系统中是否已经有一个符合要求的享元对象,如果已经存在,享元工厂则提供这个已有的享元对象;否则创建一个合适的享元对象。
客户端角色:该角色需要自行存储所有享元对象的外部状态。

两个概念:

  1. 内部状态:是存储在享元对象内部的、可以共享的信息(如棋子颜色和大小属性),并且不会随环境改变而改变(比如棋子颜色大小不会随着坐标变化而变化);

  2. 外部状态:是随着环境改变而改变且不可以共享的状态(如坐标),享元对象的外部状态必须由客户端保存(如棋子设为享元模式后所有黑色棋子都引用同一个对象了,就不能保存自己的位置了),并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部做操作。

享元模式使用场景:
1. 系统中有大量的相似对象(如棋子),这些对象耗费大量内存。
2. 细粒度的对象都具备较接近的外部状态(如棋子的坐标就是外部状态),而且内部状态(如棋子颜色和大小)与环境无关,即对象没有特定身份。
3. 需要缓冲池的场景。

棋子的类图:
享元模式(结构型)_第2张图片
实例代码:

棋子享元的工厂:

package com.shusheng.flyweight;
import java.util.concurrent.ConcurrentHashMap;
/**棋子享元的工厂*/
public class ChesspieceFactory {

    private static ConcurrentHashMap<String,ChesspieceFlyweight> conHashMap = new ConcurrentHashMap<>();//这是线程安全且效率高的容器 

    /**工厂方法用于获取棋子享元对象*/
    public static ChesspieceFlyweight getChesspieceFlyweight(String color){
        ChesspieceFlyweight flyweight = conHashMap.get(color);
        if (flyweight==null) {
            flyweight = new ChesspieceFlyweight(color);
            conHashMap.put(color,flyweight);
        }
        System.out.println("容器的容量"+conHashMap.size());
        return flyweight;
    }

}

棋子享元的抽象接口:

package com.shusheng.flyweight;

/**抽象棋子应该有的动作*/
public interface ChesspieceI {

    /**下棋动作*/
    public void put(int x,int y); 
}

棋子享元的具体实现:

package com.shusheng.flyweight;

/**棋子*/
public class ChesspieceFlyweight implements ChesspieceI{

    private String color;//棋子颜色

    ChesspieceFlyweight(String color) {//享元对象构造方法不对外公开,避免外部使用者直接new该对象。
        this.color = color;
    }

    /**下棋动作*/
    @Override
    public void put(int x, int y) {//这里简单处理,实际系统,应该要在界面画出那个棋子。
        System.out.println("我是"+color+"色棋子,我被下的位置是X="+x+",Y="+y+";\n");
    }
}

Client用于模拟享元模式的外部调用:

package com.shusheng.flyweight;

/**客户端角色,需要自己保存享元对象的所有外部状态*/
public class Client {

    public static void main(String[] args) {
        ChesspieceFlyweight chesspiece1 = ChesspieceFactory.getChesspieceFlyweight("黑");
        chesspiece1.put(10,10);//黑棋下到(10,10)

        ChesspieceFlyweight chesspiece3 = ChesspieceFactory.getChesspieceFlyweight("黑");
        chesspiece3.put(20,10);//黑棋下到(20,10)

        ChesspieceFlyweight chesspiece2 = ChesspieceFactory.getChesspieceFlyweight("白");
        chesspiece2.put(10,20);//白棋下到(10,20)

        ChesspieceFlyweight chesspiece4 = ChesspieceFactory.getChesspieceFlyweight("白");
        chesspiece4.put(20,20);//白棋下到(20,20)

    }
}

享元模式(结构型)_第3张图片
从运行结果可以看出,无论下多少个棋子,最后产生的棋子对象都只是两个,是不是比一开始的设计少了很多的对象、省了很多内存呢?

享元模式的优点:
1. 大幅度减少内存中对象的数量,降低程序的内存占用,提高性能。
缺点:
1. 享元模式增加了系统的复杂性(从上面的设计看得出来,好好的一个new的过程弄的挺复杂的);
2. 需要分外部状态和内部状态,而且内部状态具有固化特性,不应随外部状态改变而改变(比如棋子颜色大小等不随位置变化而变化),这使得程序的逻辑复杂化。
3. 享元模式将享元对象的状态外部化(指外部状态),而读取外部状态使得程序运行时间很长(比如选中一个棋子对象,就再也不能通过该对象知道它下的位置了)。

你可能感兴趣的:(flyweight,享元模式,结构型模式,Flyweight模)