书接上回,本篇讲一下结构型模式-享元设计模式
定义:提供了减少对象数量从而改善应用所需的对象结构的方式。实现方式:运用共享技术有效地支持大量细粒度的对象
要理解好享元模式,首先要明白什么叫享元,看了很多同行/大佬的说都没有明确说出享元是啥,我个人理解是共享单元,享元模式讲的就是怎么构建与维护共享单元的,姑且这么理解吧。
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 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 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 {
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>构建与维护共享池。
最后:享元模式的本质:分离与共享,对立与统一