共计 5762 个字符,预计需要花费 15 分钟才能阅读完成。
前言介绍
接下里介绍的是 Java 的设计模式之一:享元模式
咱们还是以一个问题进行开展,引入享元模式
当初有小型的外包我的项目,给客户 A 做一个产品展现网站
客户 A 的敌人们感觉成果不错,也心愿做这样的产品展现网站
然而要求都有些不同:
1. 敌人 B 要求以新闻的模式公布
2. 敌人 C 要求以博客的模式公布
3. 敌人 D 要求以微信公众号的模式公布
那么你该怎么解决呢?
一、传统形式解决问题
最简略的解决形式是:
1. 间接复制粘贴一份,而后依据客户不同要求,进行定制批改
2. 将网站复制到不同的服务器上,进行定制改良
传统计划解决网站展示我的项目 - 问题剖析
须要的 网站构造类似度很高
,而且都不是高访问量网站,如果分成多个虚拟空间来解决,相当于 一个雷同网站的实例对象很多,造成服务器的资源节约
解决思路:整合到一个网站中,共享其相干的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都能够达成共享
,缩小服务器资源
对于代码来说,因为是一份实例,保护和扩大都更加容易
下面的解决思路就能够应用 享元模式 来解决
二、什么是享元模式
享元模式(Flyweight Pattern)也叫 蝇量模式: 使用共享技术无效地反对大量细粒度的对象
罕用于零碎底层开发,解决零碎的性能问题。
像数据库连接池,外面都是创立好的连贯对象
,在这些连贯对象中有咱们须要的则间接拿来用,防止从新创立,如果没有咱们须要的,则创立一个
享元模式可能 解决反复对象的内存节约的问题
,当零碎中有大量类似对象,须要缓冲池时。
不需总是创立新对象,能够从缓冲池里拿
。这样能够升高零碎内存,同时提高效率
享元模式经典的利用场景就是 池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的利用
,享元模式是池技术的重要实现形式
比如说咱们当初有一个棋盘,有四个用户、A、B、C、D
如果每个用户观看棋盘,我都返回一个棋盘
假若一个棋盘有三百颗棋子,那么就有一千二百个棋子耗费,是不是很节约
如果说当 A 用户去观看棋盘的时候,进行缓存 那么 B、C、D 间接返回即可
是不是就能够防止类似反复对象造成的内存节约问题
享元模式原理类图剖析
FlyWeight:形象的享元角色, 他是产品的抽象类, 同时 定义出对象的内部状态和外部状态的接口或实现
ConcreteFlyWeight: 具体的享元角色,是具体的产品类,实现形象角色定义相干业务
UnSharedConcreteFlyWeight: 是不可共享的角色,个别不会呈现在享元工厂。
FlyWeightFactory:享元工厂类,用于构建一个池容器(汇合)
,同时提供从池中获取对象办法
什么是外部状态和内部状态
享元模式提出了两个要求:细粒度和共享对象
。
这里就波及到外部状态和内部状态了,行将对象的信息分为两个局部:外部状态和内部状态
比方围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有 黑白两色
,跳棋色彩多一点,所以 棋子色彩就是棋子的外部状态
;
而各个棋子之间的差异就是地位的不同,当咱们落子后,落子色彩是定的,但地位是变动的
,所以棋子 坐标就是棋子的内部状态
外部状态指对象 共享进去的信息
,存储在享元 对象外部且不会随环境的扭转而扭转
内部状态指对象 得以依赖的一个标记,是随环境扭转而扭转的
、不可共享的状态
示例阐明
举个例子:围棋实践上有 361 个空位能够放棋子,每盘棋有可能有两三百个棋子对象产生
因为内存空间无限,一台服务器很难反对更多的玩家玩围棋游戏
如果用享元模式来解决棋子,那么棋子对象就能够缩小到只有两个实例,这样就很好的解决了对象的开销问题
三、应用享元模式解决问题
那么依据咱们的思路类图,先创立抽象类 WebSite
abstract class WebSite {
// 形象办法
public abstract void use();}
接下来咱们创立不同网站的实现类继承抽象类 ConcreteWebSite
// 具体网站
class ConcreteWebSite extends WebSite {
// 共享的局部,外部状态
private String type = ""; // 网站公布的模式(类型)
// 结构器
public ConcreteWebSite(String type) {this.type = type;}
@Override
public void use() {System.out.println("网站的公布模式为:" + type + "");
}
}
接下来咱们创立工厂,依据须要压缩返回一个具体的网站
// 网站工厂类,依据须要返回压一个网站
class WebSiteFactory {
// 汇合,充当池的作用
private HashMap<String, ConcreteWebSite> pool = new HashMap<>();
// 依据网站的类型,返回一个网站, 如果没有就创立一个网站,并放入到池中, 并返回
public WebSite getWebSiteCategory(String type) {if (!pool.containsKey(type)) {
// 没有则就创立一个网站,并放入到池中
pool.put(type, new ConcreteWebSite(type));
}
return (WebSite) pool.get(type);
}
// 获取网站分类的总数 (池中有多少个网站类型)
public int getWebSiteCount() {return pool.size();
}
}
接下来咱们应用 demo 领会一下不同类型返回不同的网站
public static void main(String[] args) {
// 创立一个工厂类
WebSiteFactory factory = new WebSiteFactory();
// 客户要一个以新闻模式公布的网站
WebSite webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use();
// 客户要一个以博客模式公布的网站
WebSite webSite2 = factory.getWebSiteCategory("博客");
webSite2.use();
System.out.println("网站的分类共 =" + factory.getWebSiteCount());
}
}
运行后果如下:网站的公布模式为: 新闻
网站的公布模式为: 博客
网站的分类共 =2
然而咱们有没有思考到一个问题,这个网站具体是哪些用户在应用?
咱们的外部状态 类型是有了,然而内部状态还未有,接下来咱们创立
class User {
private String name;
public User(){}
public User(String name) {this.name = name;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
抽象类办法 WebSite 增加多一个用户应用网站的办法
abstract class WebSite {
// 形象办法
public abstract void use(User user);
}
给予咱们网站增加用户应用办法
// 具体网站
class ConcreteWebSite extends WebSite {
// 省略其余关键性代码....
@Override
public void use(User user) {System.out.println("网站的公布模式为:" + type + "在应用中 .. 使用者是" + user.getName());
}
}
接下来咱们再应用 demo 就能够看到外部类 具体使用者了
public static void main(String[] args) {
// 创立一个工厂类
WebSiteFactory factory = new WebSiteFactory();
// 客户要一个以新闻模式公布的网站
WebSite webSite3 = factory.getWebSiteCategory("新闻");
webSite3.use(new User("smith"));
// 客户要一个以博客模式公布的网站
WebSite webSite4 = factory.getWebSiteCategory("博客");
webSite4.use(new User("king"));
System.out.println("网站的分类共 =" + factory.getWebSiteCount());
}
运行后果如下:网站的公布模式为: 新闻 在应用中 .. 使用者是 smith
网站的公布模式为: 博客 在应用中 .. 使用者是 king
网站的分类共 =2
四、JDK 中享元模式源码剖析
接下来咱们应用其中剖析 JDK Integer 中的享元模式
请先看一个示例,咱们来看看 demo
public static void main(String[] args) {Integer x = Integer.valueOf(127);
Integer y = new Integer(127);
Integer z = Integer.valueOf(127);
Integer w = new Integer(127);
System.out.println(x.equals(y));
System.out.println(x == y);
System.out.println(x == z);
System.out.println(w == x);
System.out.println(w == y);
}
那么输入后果会是什么呢?
true
false
true
false
false
那为什么会造成这样的后果呢?为什么 x 与 z 为 true 呢?
先让咱们看看 Integer valueOf 这个办法,进行剖析把
public final class Integer extends Number implements Comparable<Integer> {
@Native public static final int MIN_VALUE = 0x80000000;
@Native public static final int MAX_VALUE = 0x7fffffff;
// 省略其余关键性代码.....
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
}
是否与咱们之前的工厂很类似呢?
如果 i >= IntegerCache.low && i <= IntegerCache.high 则 cache 返回
如果不在这个范畴以内,那么则 new 返回
那么 IntegerCache.low 是多少呢?IntegerCache.high 又是多少呢?
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch(NumberFormatException nfe) {// If the property cannot be parsed into an int, ignore it.}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
那么咱们依据源码就会发现,这个范畴就是 - 127 至 128
而咱们的 x = 127,z = 127 合乎这个范畴,从同一个中央返回所以为 true
public static void main(String[] args) {Integer x = Integer.valueOf(200);
Integer z = Integer.valueOf(200);
System.out.println(x == z);
}
那么这样的状况下,会返回什么呢?是 true 还是 false?
小结
在 valueOf 办法中,先判断值是否在 IntegerCache 中,如果不在,就创立新的 Integer(new), 否则,就间接从 缓存池返回
valueOf 办法,就应用到享元模式
如果应用 valueOf 办法失去一个 Integer 实例,范畴在 -128 - 127,执行速度比 new 快
五、享元模式的注意事项和细节
在享元模式这样了解,“享”就示意共享,“元”示意对象
零碎中有大量对象,这些 对象耗费大量内存,并且对象的状态大部分能够内部化时,咱们就能够思考选用享元模式
用 惟一标识码判断,如果在内存中有,则返回这个惟一标识码所标识的对象,用 HashMap/HashTable 存储
享元模式大大 缩小了对象的创立,升高了程序内存的占用,提高效率
享元模式进步了零碎的复杂度。须要拆散出外部状态和内部状态,而内部状态具备固化个性,不应该随着外部状态的扭转而扭转
,这是咱们应用享元模式须要留神的中央.
应用享元模式时,留神划分外部状态和内部状态,并且须要有一个工厂类加以控制
。
享元模式经典的利用场景是须要缓冲池的场景,比方 String 常量池、数据库连接池
参考资料
尚硅谷:设计模式(韩顺平老师):享元模式
Refactoring.Guru:《深刻设计模式》