结构型-享元模式

简介:

享元模式(Flyweight Pattern):以共享的方式高效的支持大量的细粒度对象。通过复用内存中已存在的对象,降低系统创建对象实例的性能消耗。

享元即为共享元对象,在面向对象中,创建对象是很消耗资源的,享元模式就是为避免产生大量的细粒度对象提供解决方案的。

享元模式有两种状态,内蕴状态和外蕴状态,内蕴状态存储于享元对象内部,不会随环境而改变,可以共享;外蕴状态随外部环境改变,并且由客户端保存,不能共享。

享元模式类图:

image

涉及到的角色如下:

抽象享元:为具体享元类规定出公共接口;

具体享元:实现抽象享元类接口,如果有内蕴状态,需要为内蕴状态提供存储空间,这是保证共享的必要条件;

享元工厂:负责创建和管理享元角色,保证享元对象可以被共享,例如客户端获取享元对象时,需要判断该类型对象是否存在,存在则直接返回,不存在则新建然后返回;

客户端:需要维护对所需享元对象的引用,如果有外蕴状态,需要将外蕴状态保存在客户端;

模式案例

五子棋中会用到很多的黑子和白子,如果对于每一个黑子或白子都创建一个对象会太消耗内存。可以使用享元模式,使得在整个游戏中只有“黑子”和“白子”两个对象。

首先创建一个棋子抽象类作为棋子的超类,含有一个棋子标识的属性:

/**
 * 棋子的超类,含有一个棋子类别的属性,标志具体的棋子类型
 */
public abstract class AbstractChessman {
    //棋子类别
    protected String chess;

    //构造方法
    public AbstractChessman(String chess){
        this.chess = chess;
    }

    //显示棋子信息
    public void show(){
        System.out.println(this.chess);
    }
}

黑子类:

/**
 * 需求:黑子类
 */
public class BlackChessman extends AbstractChessman {
    /*
     * 构造方法,初始化黑棋子
     */
    public BlackChessman(){
        super("●");
        System.out.println("--一颗黑棋子诞生了!--");
    }
    
}

白子类:

/**
 * 需求:白棋子
 *
 */
public class WhiteChessman extends AbstractChessman {
    /*
     * 构造方法,初始化黑棋子
     */
    public WhiteChessman(){
        super("○");
        System.out.println("--一颗白棋子诞生了!--");
    }
}

下面来设计棋子工厂类,棋子工厂类我们设计为单例模式,该类用来生产棋子对象实例,并放入缓存当中,下次再获得棋子对象的时候就从缓存当中获得。内容如下:

/**
 * 需求:棋子工厂,用于生产棋子对象实例,并放入缓存中,采用单例模式完成
 */
public class ChessmanFactory {
    //单例模式
    private static ChessmanFactory chessmanFactory = new ChessmanFactory();
    //缓存共享对象
    private final Hashtable<Character, AbstractChessman> cache = new Hashtable<Character, AbstractChessman>();
    //构造方法私有化
    private ChessmanFactory(){
    }
    //获得单例工厂对象
    public static ChessmanFactory getInstance(){
        return chessmanFactory;
    }
    /*
     * 根据字母获得棋子
     */
    public AbstractChessman getChessmanObject(char c){        
        //从缓存中获得棋子对象实例
        AbstractChessman abstractChessman = this.cache.get(c);
        //判空
        if (abstractChessman==null) {
            //说明缓存中没有该棋子对象实例,需要创建
            switch (c) {
            case 'B':
                abstractChessman = new BlackChessman();
                break;
            case 'W':
                abstractChessman = new WhiteChessman();
                break;
            default:
                System.out.println("非法字符,请重新输入!");
                break;
            }
            //如果有非法字符,那么对象必定仍为空,所以再进行判断
            if (abstractChessman!=null) {
                //放入缓存
                this.cache.put(c, abstractChessman);
            }
        }
        //如果缓存中存在棋子对象则直接返回
        return abstractChessman;
    }
}

通过客户端进行测试:

public class Test {
    public static void main(String[] args) {
        //创建工厂
        ChessmanFactory chessmanFactory = ChessmanFactory.getInstance();
        //随机数,用于生成棋子对象
        Random random = new Random();
        int radom = 0;
        AbstractChessman abstractChessman = null;
        //随机获得棋子
        for (int i = 0; i < 10; i++) {
            radom = random.nextInt(2);
            switch (radom) {
            case 0:
                //获得黑棋子
                abstractChessman = chessmanFactory.getChessmanObject('B');
                break;
            case 1:
                //获得黑棋子
                abstractChessman = chessmanFactory.getChessmanObject('W');
                break;
            }
            if (abstractChessman!=null) {
                abstractChessman.show();
            }
        }
    }
}

执行后,我们发现“一颗黑棋子诞生了!”和“一颗白棋子诞生了!”各执行了一次,说明在众多棋子中只有一个黑棋子和一个白棋子,实现了对象的共享,这就是享元。

外蕴状态

上边例子使用到了内蕴状态,内蕴状态对于任何一个享元对象来讲是完全相同的,可以说,内蕴状态保证了享元对象能够被共享。例如上边的“黑子”和“白子”,代表的状态就是内蕴状态。

如果我们为棋子添加位置信息,因为棋子位置都是不一样的,是不能够共享的,这需要使用外蕴状态。享元对象的外蕴状态与内蕴状态是两类相互独立的状态,彼此没有关联。

增加棋子位置信息的抽象类为:

/**
 * 需求:棋子的超类,含有一个棋子类别的属性,标志具体的棋子类型
 */
public abstract class AbstractChessman {
    //棋子类别
    protected String chess;
    //棋子坐标
    protected int x;
    protected int y;
    //构造方法
    public AbstractChessman(String chess){
        this.chess = chess;
    }
    //坐标设置
    public abstract void point(int x,int y);
    //显示棋子信息
    public void show(){
        System.out.println(this.chess+"("+this.x+","+this.y+")");
    }
}

完善后的黑子类:

/**
 * 需求:黑子类
 */
public class BlackChessman extends AbstractChessman {
    /*
     * 构造方法,初始化黑棋子
     */
    public BlackChessman(){
        super("●");
        System.out.println("--一颗黑棋子诞生了!--");
    }
    /*
     * 重写方法
     */
    @Override
    public void point(int x, int y) {
        this.x = x;
        this.y = y;
        this.show();
    }
}

完善后的白子类为:

/**
 * 需求:白棋子
 */
public class WhiteChessman extends AbstractChessman {
    /*
     * 构造方法,初始化黑棋子
     */
    public WhiteChessman(){
        super("○");
        System.out.println("--一颗白棋子诞生了!--");
    }
    /*
     * 重写方法
     */
    @Override
    public void point(int x, int y) {
        this.x = x;
        this.y = y;
        this.show();
    }
}

在棋子工厂中不需要进行任何改变,因为在棋子工厂中我们获得的是共享对象,外蕴状态(位置信息)是需要在客户端进行设置的。

客户端:

public class Test {
    public static void main(String[] args) {
        //创建工厂
        ChessmanFactory chessmanFactory = ChessmanFactory.getInstance();
        //随机数,用于生成棋子对象
        Random random = new Random();
        int radom = 0;
        AbstractChessman abstractChessman = null;
        //随机获得棋子
        for (int i = 0; i < 10; i++) {
            radom = random.nextInt(2);
            switch (radom) {
            case 0:
                //获得黑棋子
                abstractChessman = chessmanFactory.getChessmanObject('B');
                break;
            case 1:
                //获得黑棋子
                abstractChessman = chessmanFactory.getChessmanObject('W');
                break;
            }
            if (abstractChessman!=null) {
                abstractChessman.point(i, random.nextInt(15));
            }
        }
    }
}

需要注意的是,为了保证对象被共享,外蕴状态需要保存在客户端,也就是说,x、y值在客户端保存。

享元对象特点

享元模式的重点在于“共享对象实例,降低内存空间”,实现享元模式时,需要规划共享对象的粒度,才能降低内存空间,提高系统性能;例如如果将上边的x、y转为内蕴状态,那么享元工厂将保存非常多的对象,失去享元模式的意义。

当系统中某个对象类型的实例较多且耗费大量内存,并且对象实例分类较少,且其部分状态可以转为外蕴状态时,可以使用享元模式。单例模式本身也是一种特殊的享元模式,最大区别是单例模式的类不能被实例化,而享元模式类可以被实例化。

享元模式的缺点是,为了使对象可以共享,可能会将一些状态转为外蕴状态,使得程序复杂性增加。

Java中的享元模式

Integer类对于经常使用的-128到127范围内的Integer对象,当类被加载时就被创建了,并保存在cache数组中,一旦程序调用valueOf方法,如果i的值是在-128到127之间就直接在cache缓存数组中去取Integer对象而不是创建一个新对象,这就是享元模式的应用。

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

String类也应用了享元模式,常量池中维护的字符串可以被多个指针引用。

参考:《设计模式那点事》

目录
相关文章
|
12月前
|
设计模式 存储 缓存
结构型设计模式07-享元模式
结构型设计模式07-享元模式
32 0
|
2月前
|
设计模式 存储 数据库连接
何时使用享元模式
【8月更文挑战第22天】
18 0
|
5月前
|
Java 数据库
享元模式~
享元模式~
|
设计模式 存储 缓存
设计模式-结构型模式:享元模式
设计模式-结构型模式:享元模式
|
存储 设计模式 缓存
2023-6-28-第十式享元模式
2023-6-28-第十式享元模式
81 0
|
设计模式 缓存 前端开发
关于享元模式我所知道的
关于享元模式我所知道的
60 0
|
存储 缓存 Java
结构型模式-享元模式
结构型模式-享元模式
117 0
|
设计模式 移动开发 NoSQL
设计模式 - 结构型模式_享元模式
享元⼯⼚的设计,在⼀些有⼤量᯿复对象可复⽤的场景下,使⽤此场景在服务端减少接⼝的调⽤,在客户端减少内存的占⽤。是这个设计模式的主要应⽤⽅式。
116 0
设计模式 - 结构型模式_享元模式
|
设计模式 缓存 Java
我学会了,享元模式
享元模式属于结构型模式,这个类型的设计模式总结出了 类、对象组合后的经典结构,将类、对象的结构和使用解耦了,花式的去借用对象。
157 0
我学会了,享元模式
|
存储 设计模式 缓存