前言
《设计模式自习室》系列,顾名思义,本系列文章带你温习常见的设计模式。主要内容有:
- 该模式的介绍,包括:
- 引子、意图(大白话解释)
- 类图、时序图(理论规范)
- 该模式的代码示例:熟悉该模式的代码长什么样子
- 该模式的优缺点:模式不是万金油,不可以滥用模式
- 该模式的应用案例:了解它在哪些重要的源码中被使用
该系列会逐步更新于我的博客和公众号(博客见文章底部),也希望各位观众老爷能够关注我的个人公众号:后端技术漫谈,不会错过精彩好看的文章。
系列文章回顾
- 【设计模式自习室】开篇:为什么我们要用设计模式?
- 【设计模式自习室】建造者模式
- 【设计模式自习室】原型模式
- 【设计模式自习室】透彻理解单例模式
- 【设计模式自习室】理解工厂模式的三种形式
- 【设计模式自习室】适配器模式
- 【设计模式自习室】装饰模式
- 【设计模式自习室】桥接模式 Bridge Pattern:处理多维度变化
- 【设计模式自习室】门面模式 Facade Pattern
结构型——享元模式 Flyweight Pattern
引子
主要用于减少创建对象的数量,以减少内存占用和提高性能。
在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)用于存储具有相同内部状态的享元对象。
最经典的享元模式代码:
class FlyweightFactory {
//定义一个HashMap用于存储享元对象,实现享元池
private HashMap flyweights = newHashMap();
public Flyweight getFlyweight(String key){
//如果对象存在,则直接从享元池获取
if(flyweights.containsKey(key)){
return(Flyweight)flyweights.get(key);
}
//如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
else {
Flyweight fw = newConcreteFlyweight();
flyweights.put(key,fw);
return fw;
}
}
}
定义
运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
两个概念:
- 内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
- 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。
在享元类中要将内部状态和外部状态分开处理,通常将内部状态作为享元类的成员变量,而外部状态通过注入的方式添加到享元类中。
类图
如果看不懂UML类图,可以先粗略浏览下该图,想深入了解的话,可以继续谷歌,深入学习:
享元模式包含如下角色:
- Flyweight: 抽象享元类
- ConcreteFlyweight: 具体享元类
- UnsharedConcreteFlyweight: 非共享具体享元类
- FlyweightFactory: 享元工厂类
时序图
时序图(Sequence Diagram)是显示对象之间交互的图,这些对象是按时间顺序排列的。时序图中显示的是参与交互的对象及其对象之间消息交互的顺序。
我们可以大致浏览下时序图,如果感兴趣的小伙伴可以去深究一下:
代码实现
代码参考:
https://www.cnblogs.com/chenssy/p/3330555.html
假设:我们有一个绘图的应用程序,通过它我们可以出绘制各种各样的形状、颜色的图形,那么这里形状和颜色就是内部状态了,通过享元模式我们就可以实现该属性的共享了。
抽象享元类Flyweight:绘制图像的抽象方法
public abstract class Shape {
public abstract void draw();
}
具体享元类ConcreteFlyweight:例子中则是一种绘制某种图像(圆形)的具体实现类,里面的颜色则是一个可以共享的内部对象。
public class Circle extends Shape{
private String color;
public Circle(String color){
this.color = color;
}
public void draw() {
System.out.println("画了一个" + color +"的圆形");
}
}
享元工厂类FlyweightFactory:
利用了HashMap保存已经创建的颜色
public class FlyweightFactory{
static Map<String, Shape> shapes = new HashMap<String, Shape>();
public static Shape getShape(String key){
Shape shape = shapes.get(key);
//如果shape==null,表示不存在,则新建,并且保持到共享池中
if(shape == null){
shape = new Circle(key);
shapes.put(key, shape);
}
return shape;
}
public static int getSum(){
return shapes.size();
}
}
客户端调用:
调用相同颜色时,会直接从HashMap中取那个颜色的对象,而不会重复创建相同颜色的对象。
public class Client {
public static void main(String[] args) {
Shape shape1 = FlyweightFactory.getShape("红色");
shape1.draw();
Shape shape2 = FlyweightFactory.getShape("灰色");
shape2.draw();
Shape shape3 = FlyweightFactory.getShape("绿色");
shape3.draw();
Shape shape4 = FlyweightFactory.getShape("红色");
shape4.draw();
Shape shape5 = FlyweightFactory.getShape("灰色");
shape5.draw();
Shape shape6 = FlyweightFactory.getShape("灰色");
shape6.draw();
System.out.println("一共绘制了"+FlyweightFactory.getSum()+"中颜色的圆形");
}
}
使用场景举例
如果一个系统中存在大量的相同或者相似的对象,由于这类对象的大量使用,会造成系统内存的耗费,可以使用享元模式来减少系统中对象的数量。
Integer 中的享元模式
public static void main(String[] args) {
Integer i1 = 12 ;
Integer i2 = 12 ;
System.out.println(i1 == i2);
Integer b1 = 128 ;
Integer b2 = 128 ;
System.out.println(b1 == b2);
}
输出是
true
false
在Java中,Integer是有缓存池的,缓存了-128~127的int对象
IntegerCache 缓存类:
//是Integer内部的私有静态类,里面的cache[]就是jdk事先缓存的Integer。
private static class IntegerCache {
static final int low = -128;//区间的最低值
static final int high;//区间的最高值,后面默认赋值为127,也可以用户手动设置虚拟机参数
static final Integer cache[]; //缓存数组
static {
// high value may be configured by property
int h = 127;
//这里可以在运行时设置虚拟机参数来确定h :-Djava.lang.Integer.IntegerCache.high=250
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {//用户设置了
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);//虽然设置了但是还是不能小于127
// 也不能超过最大值
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
//循环将区间的数赋值给cache[]数组
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
其他
同理,Long也有缓存池。
String类定义为final(不可改变的),JVM中字符串一般保存在字符串常量池中,java会确保一个字符串在常量池中只有一个拷贝,这个字符串常量池在JDK6.0以前是位于常量池中,位于永久代,而在JDK7.0中,JVM将其从永久代拿出来放置于堆中。
详细可参考:
http://laijianfeng.org/2018/09/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E4%BA%AB%E5%85%83%E6%A8%A1%E5%BC%8F%E5%8F%8A%E5%85%B8%E5%9E%8B%E5%BA%94%E7%94%A8/
优缺点
优点
- 享元模式的优点在于它能够极大的减少系统中对象的个数。
- 享元模式由于使用了外部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。
缺点
- 由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。
参考
- 《HEAD FIRST设计模式》
- https://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/flyweight.html
- https://www.cnblogs.com/chenssy/p/3330555.html
- http://laijianfeng.org/2018/09/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E4%BA%AB%E5%85%83%E6%A8%A1%E5%BC%8F%E5%8F%8A%E5%85%B8%E5%9E%8B%E5%BA%94%E7%94%A8/
关注我
我是一名后端开发工程师。
主要关注后端开发,数据安全,爬虫,物联网,边缘计算等方向,欢迎交流。
各大平台都可以找到我
原创博客主要内容
- Java面试知识点复习全手册
- 设计模式/数据结构 自习室
- Leetcode/剑指offer 算法题解析
- SpringBoot/SpringCloud菜鸟入门实战系列
- 爬虫相关技术文章
- 后端开发相关技术文章
- 逸闻趣事/好书分享/个人兴趣
个人公众号:后端技术漫谈
如果文章对你有帮助,不妨收藏,投币,转发,在看起来~