1 Flyweight Pattern 享元模式
目的:减少创建对象的数量,减少内存占用和提高性能;
实现:用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。
1.运用共享技术有效地支持大量细粒度对象的复用;系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用,如果未找到匹配的对象,则创建新对象;
2.享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区分了内部状态(Intrinsic State)和外部状态(Extrinsic State)。
2 实现
代码场景:生成一个线程池,预定义了线程名字,如果客户端获取的线程存在则返回线程池中的该线程,如果不存在则新建一个线程。
2.1 代码实现
抽象享元类:AbstractThread
public abstract class AbstractThread {
// 线程内部状态
protected String threadName;
public AbstractThread(String threadName) {
this.threadName = threadName;
}
// 线程外部状态
public void threadStatus(String threadStatus) {
System.out.println(threadName + "当前外部状态为:" + threadStatus);
}
public abstract void excute();
}
具体享元类:ThreadA
public class ThreadA extends AbstractThread {
public ThreadA(String threadName) {
super(threadName);
}
@Override
public void excute() {
System.out.println("线程[" + threadName + "]开始执行任务~");
}
}
具体享元类:ThreadB
public class ThreadB extends AbstractThread {
public ThreadB(String threadName) {
super(threadName);
}
@Override
public void excute() {
System.out.println("线程[" + threadName + "]开始执行任务~");
}
}
具体享元类:ThreadNew 匹配不到时新增
public class ThreadNew extends AbstractThread {
public ThreadNew(String threadName) {
super(threadName);
}
@Override
public void excute() {
System.out.println("线程[" + threadName + "]开始执行任务~");
}
}
享元工厂类:ThreadFactory
public class ThreadFactory {
private static ThreadFactory threadFactory = new ThreadFactory();
private Map<String, AbstractThread> threadMap = new HashMap<String, AbstractThread>();
private ThreadFactory() {
AbstractThread threadA = new ThreadA("A");
AbstractThread threadB = new ThreadB("B");
threadMap.put("A", threadA);
threadMap.put("B", threadB);
}
public static ThreadFactory getThreadFactory() {
return threadFactory;
}
public AbstractThread getThread(String threadName) {
// 如果想要获取的线程享元池中存在 则从享元池中获取
if (threadMap.containsKey(threadName)) {
return threadMap.get(threadName);
}
// 如果想要获取的线程享元池中不存在 则new一个
else {
ThreadNew threadNew = new ThreadNew(threadName);
threadMap.put(threadName, threadNew);
return threadNew;
}
}
}
2.2 涉及角色
在享元模式结构图中包含如下几个角色:
Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。
享元的内部状态和外部状态简单介绍:
(1) 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。如字符的内容,不会随外部环境的变化而变化,无论在任何环境下字符“a”始终是“a”,都不会变成“b”。(2) 外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。如字符的颜色,可以在不同的地方有不同的颜色,例如有的“a”是红色的,有的“a”是绿色的,字符的大小也是如此,有的“a”是五号字,有的“a”是四号字。而且字符的颜色和大小是两个独立的外部状态,它们可以独立变化,相互之间没有影响,客户端可以在使用时将外部状态注入享元对象中。
正因为区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。
2.3 调用
调用者:
public class Client {
public static void main(String[] args) {
// 获取享元工厂
ThreadFactory threadFactory = ThreadFactory.getThreadFactory();
// 获取共享元素
AbstractThread thread1 = threadFactory.getThread("A");
AbstractThread thread2 = threadFactory.getThread("B");
// 获取新元素
AbstractThread thread3 = threadFactory.getThread("C");
// 线程执行任务
thread1.excute();
thread2.excute();
thread3.excute();
AbstractThread thread4 = threadFactory.getThread("A");
// thread1和thread4获取的是同一个实例
if (thread1 == thread4) {
System.out.println("thread1 == thread4");
}
}
}
结果:
线程[A]开始执行任务~
线程[B]开始执行任务~
线程[C]开始执行任务~
thread1 == thread4
参考文献:
[ 1 ] 图解设计模式/(日)结城浩著;杨文轩译。–北京:人民邮电出版社,2017.1.
[ 2 ] 维基百科 设计模式
[ 3 ] 极客学院WIKI–设计模式.
[ 4 ] 菜鸟教程–设计模式.