享元模式: 以共享的方式高效的支持大量的细粒度对象。 通过复用内存中已存在的对象,降低系统创建对象实例的性能消耗。在面向对象中,大量细粒度对象的创建、销毁及存储所造成的资源和性能上的损耗,可能会在系统运行时形成瓶颈。那么该如何避免产生大量的细粒度对象,同时又不影响系统使用面向对象的方式进行操作呢?享元模式提供了一个比较好的解决方案。
享元模式的结构图
享元模式中的几个角色
- Flyweight(抽象享元类): 通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
- ConcreteFlyweight(具体享元类): 它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
- UnsharedConcreteFlyweight(非共享具体享元类): 并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
- FlyweightFactory(享元工厂类): 享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。
示例
有相似的网站商家客户,要求的功能大多都是相同的也就是信息发布、产品展示、博客留言的功能,要求的差别并不大。
应用享元模式将网站抽象成享元类,再设置具体的网站享元类。代码如下:
Flyweight(抽象享元类)
public abstract class WebSize {
public abstract void use(User user);
}
ConcreteFlyweight(具体享元类)
public class ConcreteWebSize extends WebSize{
private String name="";
public ConcreteWebSize(String name) {
super();
this.name = name;
}
@Override
public void use(User user) {
// TODO Auto-generated method stub
System.out.println("网站分类:"+name+"用户:"+user.getName());
}
}
FlyweightFactory(享元工厂类)
import java.util.HashMap;
public class WebSizeFactory {
private static HashMap<String,WebSize> flyweights = new HashMap<String,WebSize>();
//获得网站分类
public WebSize getWebSizeCategory(String key) {
if(!flyweights.containsKey(key)) {
WebSize flyweight = new ConcreteWebSize(key);
flyweights.put(key, flyweight);
}
return flyweights.get(key);
}
public int getWebSizeCount() {
return flyweights.size();
}
}
客户端Main
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
WebSizeFactory wf = new WebSizeFactory();
WebSize web1 = wf.getWebSizeCategory("产品展示");
web1.use(new User("张三"));
WebSize web2 = wf.getWebSizeCategory("产品展示");
web2.use(new User("李四"));
WebSize web3 = wf.getWebSizeCategory("产品展示");
web3.use(new User("王五"));
WebSize web4 = wf.getWebSizeCategory("博客");
web4.use(new User("大神1"));
WebSize web5 = wf.getWebSizeCategory("博客");
web5.use(new User("大神2"));
WebSize web6= wf.getWebSizeCategory("博客");
web6.use(new User("大神3"));
System.out.println("网站分类总数为:"+wf.getWebSizeCount());
}
}
代码运行结果为:
这样写算是基本实现了享元模式的共享对象的目的,也就是说,不管建几个网站,只要是产品展示 都是一样的,只要是‘博客’ , 也是完全相同的,即使当不同的企业的时候用户不同,它们的数据不会相同此时用户就是外部状态,能实现相应的功能。
认识享元
变与不变
- 享元模式设计的重点就在分离变与不变。把一个对象的状态分成内部状态和外部状态,内部状态是不变的,外部状态是可变的。然后通过共享不变的部分,达到减少对象数量并节约内存的目的。
- 分离变与不变是软件设计上最基本的方式之一,比如预留接口:一个常见的原因就是这旦存在变化,可能在今后需要扩展或者是改变已有的实现,因此 预留接口作为“可插入性的保证。
享元对象能做到共享的关键是区分内部状态( Internal State )和外部状态( External State ) - 内部状态是存储在享元对象内部并且不会随环境改 变而改变。因此内部状态可以共享。
- 外部状态是随环境改变而改变的、不可以共享的状 态。享元对象的外部状态必须由客户端保存,并在 享元对象被创建之后,在需要使用的时候再传入到
享元对象内部。 - 外部状态与内部状态是相互独立的。
享元模式的优点
- 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
- 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
享元模式的缺点
- 享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
- 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
适用场景:
- 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
- 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
本片博客转载了博主https://blog.csdn.net/wwwdc1012/article/details/82833965的许多对享元模式的介绍。