书接上回,本篇讲一下结构型模式-享元设计模式

享元设计模式

定义:提供了减少对象数量从而改善应用所需的对象结构的方式。实现方式:运用共享技术有效地支持大量细粒度的对象

要理解好享元模式,首先要明白什么叫享元,看了很多同行/大佬的说都没有明确说出享元是啥,我个人理解是共享单元,享元模式讲的就是怎么构建与维护共享单元的,姑且这么理解吧。

UML

IFlyweight:享元接口,定制共享单元的操作规则(操作方法),一般会明确需要指定参数,按照模式的讲法,这个参数称之为外部属性,享元对象通过这个属性与外界环境(外界对象交互)。

ConcreteFlyweight:具体享元实现对象,必须是可以共享的,需要封装共享单元内部状态。

解释下:
共享单元内部属性:共享单元自己内部定义的属性,当实例创建好之后不允许再改变了,并且这个属性可以认为是该共享单元的身份识别码,同一个共享体系中,身份识别码唯一。
共享单元是可共享的:共享体现中会创建很多结构类似、功能类似对象实例,这肯定耗内存,此时可以提前将这些实例抽象出来,形成共享单元集合,后续需要需要时,根据身份识别码获取满足条件的共享单元,并完成操作。(这描述是不是很像线程池/数据库连接池的线程/连接呢?其实就是同一个东西)

UnsharedConcreteFlyweight:非共享的享元实现对象,并非所有IFlyweight实现对象都是共享的,也有独立的个体,不做共享,这个实现不多。

FlyweightFactory:享元工厂,主要用于创建并管理共享的享元对象,并对外提供访问共享享元的接口。

IFlyWeight 

/**
 * 享元接口
 */
public interface IFlyWeight {

    //行为操作, 参数为外部状态
    void operation(String extrinsieState);
}

ConcreteFlyWeight  

/**
 * 具体享元
 */
public class ConcreteFlyWeight implements IFlyWeight{
    //内部状态
    private String intrinsicState;

    public ConcreteFlyWeight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }

    @Override
    public void operation(String extrinsieState) {
        System.out.println("ConcreteFlyWeight---内部:" + intrinsicState + "---外部:" + extrinsieState);
    }
}

UnsharedConcreteFlyWeight  

/**
 * 非共享享元具体
 */
public class UnsharedConcreteFlyWeight implements IFlyWeight{
    //全状态
    private String allState;

    @Override
    public void operation(String extrinsieState) {
        System.out.println("UnsharedConcreteFlyWeight---全状态:" + allState + "---外部:" + extrinsieState);
    }
}

FlyweightFactory  

/**
 * 享元工具类
 */
public class FlyweightFactory {
    //享元池
    private Map<String, IFlyWeight> fsmap = new HashMap<>();
    //获取享元,如果有获取,如果没有添加
    public IFlyWeight getFlyweight(String key){
        if(!fsmap.containsKey(key)){
            fsmap.put(key, new ConcreteFlyWeight(key));
        }
        return fsmap.get(key);
    }
}

案例分析

需求:抽5扑克牌张。牌数搭配:4个花色与点数
约定:花色是固定4个,点数是随机可变的,最大1,最小13

ICard 

/**
 * 卡牌:共享单元接口
 */
public interface ICard {
    //设置数字:外部属性,后续可以变动
    void setNum(int num);

    //显示花色 + 数字
    void show();
}

PlayCard  

/**
 * 卡牌:实际享元
 */
public class PlayCard implements ICard{

    //花色:内在属性,共享单元身份标记,确定之后不可变
    private String colour;

    //数字:外部属性, 通过set方法设置
    private int  num;

    public PlayCard(String colour) {
        this.colour = colour;
    }

    @Override
    public void setNum(int num) {
        this.num = num;
    }

    @Override
    public void show() {
        System.out.println("抽中卡牌:" + colour + ",点数:" + num);
    }
}

CardFactory  

/**
 * 卡片管理工厂
 */
public class CardFactory {
    //卡牌所有花色
    public static final String[] colours = new String[]{"红桃","黑桃","方块","梅花"};

    //卡牌池:享元池
    private Map<String, ICard> map = new HashMap<>();
    //抽牌
    public ICard takeout(){
        //随机抽取花色
        int i = new Random().nextInt(colours.length);
        String color = colours[i];
        //随机抽取点数
        int num = new Random().nextInt(13) + 1;
        if(!map.containsKey(color)){
            map.put(color, new PlayCard(color));
        }
        ICard iCard = map.get(color);
        iCard.setNum(num);
        return iCard;
    }
}

测试

public class App {

    public static void main(String[] args) {

        CardFactory cardFactory = new CardFactory();

        cardFactory.takeout().show();
        cardFactory.takeout().show();
        cardFactory.takeout().show();
        cardFactory.takeout().show();
        cardFactory.takeout().show();

    }
}

结果

抽中卡牌:红桃,点数:3
抽中卡牌:黑桃,点数:6
抽中卡牌:梅花,点数:3
抽中卡牌:方块,点数:9
抽中卡牌:红桃,点数:1

解析

从代码上看,CardFactory类colours属性约定花色固定4个,CardFactory类map属性维护共享池,key值花色,value是共享单元:PlayCard,刚开始共享池是空,随着后续抽牌,共享池会创建4种花色的共享单元,并存储。当共享池满之后(筹齐4种花色共享单元),继续抽牌就不再创建共享单元了,直接从池中获取,以达到共享的目的。

从共享池中获取的PlayCard共享单元中,colour固定的不允许改动,我们称之为内部属性,而num是可以修改了,根据外界赋值而变动,我们称之为外部属性。

适用场景

常常应用于系统底层的开发,以便解决系统的性能问题
系统有大量相似对象、需要使用缓冲池的场景

优缺点

优点
减少对象的创建,降低内存中对象的数量,降低系统的内存,提高效率
减少内存之外的其他资源占用

缺点
关注内/外部状态,关注线程安全问题
使系统,程序的逻辑复杂化

开发案例

可以从JDK中找例子--Integer

public final class Integer extends Number implements Comparable<Integer> {
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];
        int h = 127;
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);
        }
    }

}

看上面的代码,Integer类中它维护了一个私有的静态内部类:IntegerCache ,这个类特点是内部维护一个Integer cache[] 数组,数组中元素初始化值从最小的-128 到最大的127,并且都是Integer类型。

自动装箱

Integer aa = 1;

我们都知道java Integer类型有自动装箱逻辑,其他是调用valueOf方法, 看源码

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

发现问题没,当需要装箱数据值大于等于IntegerCache类约定最小值(-128),并且小于等于IntegerCache类约定最大值(127)时,并没有直接new Integer,而是直接从缓存中获取。

所以这里可以确定Integer类就使用的享元设计模式里面了。

IntegerCache --- 享元工厂----Flyweight

-128<=Integer<=127  --- 具体共享单元--- ConcreteFlyweight

Integer<-128 或者 Integer > 127----具体非共享单元---UnsharedConcreteFlyweight

Integer 中value值就是:内在状态不可以变

总结

享元模式核心:

1>分清共享与非共享

2>分清内在状态与外在状态

3>分清变与不变

4>构建与维护共享池。

最后:享元模式的本质:分离与共享,对立与统一