以展示网站项目引入享元模式
小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:
- 有客户要求以新闻的形式发布
- 有客户人要求以博客的形式发布
- 有客户希望以微信公众号的形式发布
传统思路
直接复制粘贴一份,然后根据客户不同要求,进行定制修改
给每个网站租用一个空间
问题分析
需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费。
解决思路
整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源。
对于代码来说,由于是一份实例,维护和扩展都更加容易。
上面的解决思路就可以使用 享元模式 来解决。
一、享元模式
1、基本介绍
1)享元模式(Flyweight Pattern)也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象
2)常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个。
3)享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
4)享元模式经典的应用场景就是池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用。
类图分析
先来认识两个概念:
内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。
由于享元模式区分了内部状态和外部状态,所以我们可以通过设置不同的外部状态使得相同的对象可以具备一些不同的特性,而内部状态设置为相同部分。在我们的程序设计过程中,我们可能会需要大量的细粒度对象来表示对象,如果这些对象除了几个参数不同外其他部分都相同,这个时候我们就可以利用享元模式来大大减少应用程序当中的对象。
涉及到的角色:
- 抽象享元(Flyweight):抽象的享元角色,它是产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现
- 具体享元(ConcreteFlyweight):实现抽象享元角色所规定出的接口。
- 复合享元(ConcreteCompositeFlyweight) :不可共享的角色,一般不会出现在享元工厂
- 享元工厂(FlyweightFactory)角色 :负责创建和管理享元角色。必须保证享元对象可以被系统适当地共享,当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象,如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
2、代码实现
具体思路
定义的网站WebSite
public abstract class WebSite {
public abstract void use(User user);
}
使用用户类,外部状态
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
网站的实现子类ConcreteWebSite
public class ConcreteWebSite extends WebSite{
//共享的部分,内部状态
private String type = "";
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println("网站的发布形式" + type + "在使用中" + " " + user.getName());
}
}
享元工厂
public 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();
}
}
测试使用
public class Client {
public static void main(String[] args) {
WebsiteFactory factory = new WebsiteFactory();
//用户1需要公众号网站
WebSite webSite1 = factory.getWebSiteCategory("公众号");
webSite1.use(new User("小红"));
//用户2需要公众号网站;
WebSite webSite2 = factory.getWebSiteCategory("公众号");
webSite2.use(new User("小明"));
//用户3需要论坛网站;
WebSite webSite3 = factory.getWebSiteCategory("论坛");
webSite3.use(new User("小张"));
//用户4需要大数据网站;
WebSite webSite4 = factory.getWebSiteCategory("大数据");
webSite4.use(new User("小赵"));
System.out.println("<=====此时实际使用的网站数量==> " + factory.getWebsiteCount());
}
}
结果
网站的发布形式公众号在使用中 小红
网站的发布形式公众号在使用中 小明
网站的发布形式论坛在使用中 小张
网站的发布形式大数据在使用中 小赵
<=====此时实际使用的网站数量==> 3
二、享元模式在 Interger 的应用源码分析
看下面这个案例
public class Demo {
public static void main(String[] args) {
Integer integer0 = new Integer(127);
Integer integer1 = Integer.valueOf(127);
Integer integer2 = Integer.valueOf(127);
System.out.println(integer0 == integer1); // false
System.out.println(integer1 == integer2); // true
System.out.println("---------------------------");
Integer integer3 = Integer.valueOf(128);
Integer integer4 = Integer.valueOf(128);
System.out.println(integer3 == integer4); // false
}
}
valueOf() 方法就用到了享元模式
用 new 的方式创建时,直接创建一个新的对象实例;。
public Integer(int value) {
this.value = value;
}
用 valueOf() 方法时,会先判断是否在缓冲池中的范围内,不在时就去 new 创建对象。
缓冲池区间在 -128~127
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
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() {}
}
三、享元模式的注意事项和细节
1)享元模式可以这样理解,“享”就表示共享,“元”表示对象。
2)系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式。
3)用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable 存储
4)享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
5)享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方。