开始介绍之前,先分析一个实例:
现有一个小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求有些不同:
·有客户要求以新闻的形式发布
·有客户要求以微博的形式发布
·有客户希望以微信公众号的形式发布
传统解决方案:直接复制一份,然后根据不同的客户需求进行定制修改。给每个网站分配一个虚拟空间。
对上述解决方案进行分析:
(1) 需要网站结构的相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费。
(2)解决思路:整合到一个网站中,共享其相关的代码数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源。即使用享元模式进行修改。这样对于代码来说,由于是一份实例,维护和扩展都更加容易。
基本介绍
(1)享元模式也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象。
(2)常用于系统底层的开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个。
(3)享元模式可以解决重复对象的内存浪费问题,当系统中有大量相似的对象,需要缓冲池时,不需要总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率。
(4)享元模式经典的应用场景便是池技术,String常量池,数据库连接池,缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式。
原理图:
原理图说明:
(1)Flyweight:抽象的享元角色,它是产品的抽象类,同时定义出对象的外部状态和内部状态的接口或者实现。
(2)ConcreteFlyweight:具体的享元角色,是具体的产品类,实现抽象角色定义的相关业务。
(3)UnsharedConcreteFlyweight:不可共享的角色,一般不会出现在享元工厂FlyweightFactory。
(4)FlyweightFactory:享元工厂,用于构建一个池容器(集合),同时提供获取对象的相关方法。
内部对象和外部对象:
内部对象:指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变。
外部对象:指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
比如:围棋棋子只有黑白两种颜色,各个棋子的位置不同,棋子的颜色是固定的,但是位置是变化的,因此棋子颜色就是棋子的内部状态,棋子坐标就是棋子外部状态。
实例分析
上述实例使用享元模式实现:
// 外部状态
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;
}
}
// 抽象的网站角色
public abstract class WebSite {
public abstract void use(User user);
}
// 具体的网站角色
public class ConcreteWebSite extends WebSite {
// 共享的对象,内部状态
private String type = ""; // 网站发布类型
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println(user.getName() + "要求网站的发布形式为:" + type);
}
}
// 网站工厂, 根据需求返回一个具体的网站
public class WebSiteFactory {
private HashMap<String, ConcreteWebSite> webSitePool = new HashMap<>();
// 根据网站类型返回一个网站,如果没有就创建一个网站,并放入到池中
public WebSite getWebSiteCategory(String type) {
if (!webSitePool.containsKey(type)) {
webSitePool.put(type, new ConcreteWebSite(type));
}
return webSitePool.get(type);
}
// 获取网站分类的总数
public int getWebSiteCount() {
return webSitePool.size();
}
}
// 主方法
public static void main(String[] args) {
WebSiteFactory webSiteFactory = new WebSiteFactory();
WebSite webSite1 = webSiteFactory.getWebSiteCategory("新闻");
webSite1.use(new User("用户1"));
WebSite webSite2 = webSiteFactory.getWebSiteCategory("微博");
webSite2.use(new User("用户2"));
WebSite webSite3 = webSiteFactory.getWebSiteCategory("新闻");
webSite3.use(new User("用户3"));
System.out.println(webSiteFactory.getWebSiteCount());
/**
* 用户1要求网站的发布形式为:新闻
* 用户2要求网站的发布形式为:微博
* 用户3要求网站的发布形式为:新闻
* 网站类型2
* */
}
总结
(1)Integer中的valueOf方法使用了享元模式。如果Integer.valueOf(x) x在-128~127之间,便是用享元模式返回。(具体见源码)
Integer x = Integer.valueOf(127);
Integer y = Integer.valueOf(127);
System.out.println(x == y); // true
Integer z = Integer.valueOf(128);
Integer w = Integer.valueOf(128);
System.out.println(z == w); // false
(2)系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,就可以考虑使用享元模式。
(3)享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率。
(4)享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变。
(5)使用享元模式,需划分内部状态和外部状态,并且需要一个工厂类加以控制。