1. 模式引出: 展示网站项目需求
小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:
- 有客户要求以新闻的形式发布
- 有客户人要求以博客的形式发布
- 有客户希望以微信公众号的形式发布
- 访问量不高
1.1 传统方案解决网站展现项目
- 每个网站数据相同
- 将数据作为父类,每增加一种表现形式,就增加一个子类
- 一个网站可以有多个用户,一个用户可以浏览多个网站
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200513105438127.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwOTYzMDc2,size_16,color_FFFFFF,t_70)
1.2 传统方案解决网站展现项目-问题分析
-
需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费
-
解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源
-
对于代码来说,由于是一份实例,维护和扩展都更加容易
-
上面的解决思路就可以使用 享元模式 来解决
2. 享元模式基本介绍
-
享元模式(Flyweight Pattern) 也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象
-
常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
-
享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
-
享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式
String s = "hello";
String s2 = new String("hello");
System.out.println(s == s2); // true
2.1 享元模式的原理类图
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200513105453331.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwOTYzMDc2,size_16,color_FFFFFF,t_70)
对原理图的说明-即(模式的角色及职责)
-
FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态(后面介绍) 的接口或实现
-
ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
-
UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。( 要区分可共享和不可共享的部分)
-
FlyWeightFactory享元工厂,用于构建一个池容器(集合),同时提供从池中获取对象方法
2.2 内部状态和外部状态 ( 可共享和不可共享 )
-
享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态
-
内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
-
外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
3. 方案修改
修改后的UML类图
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200513105507848.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwOTYzMDc2,size_16,color_FFFFFF,t_70)
WebSite.java
public abstract class WebSite {
public abstract void use(User user);
}
ConcreteWebSite.java
// 具体网站
public class ConcreteWebSite extends WebSite {
// 共享部分(内部状态)
private String type;
@Override
public void use(User user/* User为非共享,外部状态 */) {
System.out.println("网站形式为 " + type + " " + user.getName() + "在使用");
}
public ConcreteWebSite(String type) {
super();
this.type = type;
}
}
WebSiteFactory.java
// 工厂类,返回一个网站
public class WebSiteFactory {
// 集合,充当池
private HashMap<String, ConcreteWebSite> pool = new HashMap<String, ConcreteWebSite>();
public WebSite getWebSiteCategory(String type) {
if (!pool.containsKey(type)) {
pool.put(type, new ConcreteWebSite(type));
}
return pool.get(type);
}
// 获取数量
public int getWebSiteCount() {
return pool.size();
}
}
User.java
// 外部状态 非共享
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(String name) {
super();
this.name = name;
}
}
测试Main.java
public class Main {
public static void main(String[] args) {
WebSiteFactory factory = new WebSiteFactory();
WebSite webSite = factory.getWebSiteCategory("新闻");
webSite.use(new User("echo"));
WebSite webSite2 = factory.getWebSiteCategory("博客");
webSite2.use(new User("tom"));
WebSite webSite3 = factory.getWebSiteCategory("博客");
webSite3.use(new User("geek"));
System.out.println("网站对象总数为 "+ factory.getWebSiteCount());
}
}
输出结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200513105522203.png)
4. 享元模式在JDK1.8中Interger的应用源码分析
首次先看一段代码(非源码)
FlyWeight.java
public class FlyWeight {
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)); // true
System.out.println(x == y); // false
System.out.println(x == z); // true
System.out.println(w == x); // false
System.out.println(w == y); // false
}
}
第一个比较的是值,所以结果肯定是true
后面都是比较引用,只要带有new的都是新申请的空间,引用肯定不一样
那为什么x == z比较引用是true?
我们可以定位x和z的创建方法
Integer x = Integer.valueOf(127);
Integer z = Integer.valueOf(127);
创建方法是一样的
肯定是这个Integer的valueOf方法里搞了点事情,我们追到valueOf方法里瞧一瞧
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
的时候,就会返回IntegerCache.cache[i + (-IntegerCache.low)]
,否则就根据 i
的值新建一个Integer
对象返回。
那么,IntegerCache.low
和IntegerCache.high
是啥?
继续追Integer
源码,可以看到low
和high
定义的位置,可以发现一个静态内部类,这里定义了low
和high
private static class IntegerCache {
static final int low = -128;
static final int high;
...
static {
int h = 127;
...
}
low的值和high的值默认分别是-128和127,这不是一个字节的大小范围吗,定义这个有什么用处?
可以看到注释
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
* (缓存以支持JLS要求的-128到127(含)之间的值的自动装箱的对象标识语义。---来自google翻译)
**/
(就是方便支持自动装箱的意思 )这个看看了解就好,文章重点不是这个
回到valueOf
方法,如果i
在low
和high
之间,就返回IntegerCache.cache[i + (-IntegerCache.low)]
,这个IntegerCache.cache
明显是个数组,继续追数组的定义
private static class IntegerCache {
...
static final Integer cache[];
...
static {
...
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
...
}
...
可以看到cache
数组定义在IntegerCache
类中,大小可以存放low
到high
的所有值,并且从低位到高位逐位赋值,也就是说,当调用cache数组是,该数组就会被初始化,空间是满的。再看return IntegerCache.cache[i + (-IntegerCache.low)]
,返回该数组中的对象,也就是说,如果i
在low
到high
的范围,可以从数组中获取,而不需要创建(可以看作是缓存)
可以看出,主要的思想就是,首相建立一个缓存(数组),如果刚好范围符合,可以直接取数组中的对象,如果不再范围内,那就新建一个。
测试一下在范围之外的200
Integer a = Integer.valueOf(200);
Integer b = Integer.valueOf(200);
System.out.println(a == b);
输出结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200513105606297.png)
5. 享元模式的注意事项和细节
-
在享元模式这样理解,“享”就表示共享,“元”表示对象
-
系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
-
用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
-
享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
-
享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.
-
使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
-
享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池。