浅谈设计模式-享元模式
书接上回,本篇讲一下结构型模式-享元设计模式
享元设计模式
定义:提供了减少对象数量从而改善应用所需的对象结构的方式。实现方式:运用共享技术有效地支持大量细粒度的对象
要理解好享元模式,首先要明白什么叫享元,看了很多同行/大佬的说都没有明确说出享元是啥,我个人理解是共享单元,享元模式讲的就是怎么构建与维护共享单元的,姑且这么理解吧。
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>构建与维护共享池。
最后:享元模式的本质:分离与共享,对立与统一
相关文章
- 设计模式-解释器模式
- 从“假如有下面几种价格10,20,50,请你代码实现将他们排序输出”看设计模式中的策略模式
- JS设计模式一:单例模式
- 设计模式_命令模式
- 《惢客创业日记》2020.01.15(周三)二次学习Costco模式(三)
- NAT模式实现虚拟机共享主机网络
- 159 python网络编程 - 单进程服务器(非堵塞模式)
- 设计模式java——装饰模式
- 设计模式--行为型模式--策略模式
- 设计模式:解释器模式
- 最精简:设计模式之模板方法模式
- 《JavaScript设计模式》——第10章 水管弯弯——适配器模式 10.1引入jQuery
- 《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.5 外观模式
- Python 教程之在 4 分钟内用 Python 编写复杂模式的简单方法。
- 《 测试反模式:有效规避常见的92种测试陷阱》——1.5 测试的局限性
- 《软件建模与设计: UML、用例、模式和软件体系结构》一一2.5 交互图
- 用sed、awk、grep同时匹配多个条件(与模式、或模式)
- 中芯国际获紫光增持 不会改变中芯国际运营模式
- 浅析前端安全-如何防止CSRF攻击:csrf安全漏洞是什么、发生背景、常见攻击类型(get、post、链接)、csrf的防护策略(同源策略-origin/referer、CSRF Token、双重cookie验证、Samesite Cookie属性-严格/宽松模式区别)
- Java设计模式之结构型:享元模式
- 设计模式(三)代理模式
- C中操作文件的几种模式
- 【设计模式】模板方法模式
- 【设计模式】组合模式
- 【设计模式专题】用英雄联盟案例来讲解—桥接模式
- 设计模式(九):Composite组合模式 -- 结构型模式
- 设计模式-模板模式
- 工厂方法模式练习:工厂方法模式在农场系统中的实现(IDEA)