1 展示网站的项目需求
我们给客户做了一个产品展示的网站,客户的朋友觉得效果不错,也希望我们帮他做一个这样的网站,但是要求有些不同,希望网站以新闻的形式发布,但是有些朋友希望以博客的形式发布,还有一些朋友希望以微信公众号的形式发布。面对这样一个需求我们应该如何处理呢?
通常的做法是直接复制粘贴一份,然后根据客户的不同要求进行定制修改,给每一个网站租用一个空间。但是这种解决方法有一个问题:创建出的这些网站相似度很高,而且都不是高访问量的网站,如果分成多个虚拟空间来处理,相当于一个相同的网站的实例对象很多,造成服务器资源的浪费。
解决这个问题也很简单,就是将其整合到一个网站中,共享相关代码和数据,对硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源的浪费。
2 什么是享元模式
享元模式(Flyweight Pattern)也叫蝇量模式,运用共享技术有效地支持大量细粒度的对象,常用于系统底层开发,解决系统性能问题。像数据库链接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建。享元模式能够解决重复对象的内存浪费问题,它的经典实用场景就是池技术,像String常量池、Integer常量池、数据库连接池等,都是利用了享元模式。
角色分析:
1)Flyweight:抽象的享元角色,它是产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现。内部状态是指存储在享元对象内部且不会随环境的改变而改变。外部状态是对象依赖的一个标记,是随环境而改变的,是不可共享的状态。
2)ConcreteFlyweight:具体的享元角色,也就是具体额产品类,实现抽象角色定义的相关业务。
3)UnsharedConcreteFlyweight:不可共享的角色,通常不会出现在享元工厂中。
4)FlyweightFactory:享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象的方法。

3 代码实现享元模式
首先有一个抽象的享元角色,网站的抽象类:
public abstract class WebSite {
public abstract void use(User user);
}
网站的具体实现类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());
}
}
享元工厂类WebSiteFactory:
/**
* 网站工厂类,根据需要返回一个网站实例
*/
public class WebSiteFactory {
// 集合,充当池的作用
private HashMap<String, WebSite> pool = new HashMap<>();
// 根据网站类型,返回一个网站,如果没有就创建一个并放入池子中
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();
}
}
客户端:
public class Client {
public static void main(String[] args) {
// 创建一个工厂类
WebSiteFactory factory = new WebSiteFactory();
// 客户以新闻形式发布网站
WebSite webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use(new User("张三"));
// 客户以博客形式发布网站
WebSite webSite2 = factory.getWebSiteCategory("博客");
webSite2.use(new User("李四"));
// 客户以博客形式发布网站
WebSite webSite3 = factory.getWebSiteCategory("博客");
webSite3.use(new User("王五"));
// 客户以博客形式发布网站
WebSite webSite4 = factory.getWebSiteCategory("博客");
webSite4.use(new User("赵六"));
// 网站总数
System.out.println("网站分类总数: " + factory.getWebSiteCount());
}
}
输出结果:
网站的发布形式为:新闻, 使用者:张三
网站的发布形式为:博客, 使用者:李四
网站的发布形式为:博客, 使用者:王五
网站的发布形式为:博客, 使用者:赵六
网站分类总数: 2
可以看到,虽然网站使用了4次,但是发布形式只有两种,实际创建出的网站对象只有两个。

4 享元模式的特点和注意事项
1)当系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,可以考虑使用享元模式。
2)享元模式提高了系统的复杂度,需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变。
3)使用享元模式时,要注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
4)享元模式的经典应用场景是需要缓冲池的场景,如String常量池、数据库连接池等。