设计模式学习笔记(十四)享元模式
概念
享元模式(Flyweight Pattern)是一种结构型设计模式,用于优化大量细粒度对象的共享与重复利用,以减少内存占用和提高性能。
在某些情况下,一个应用程序可能需要创建大量相似的对象,这些对象之间存在相同或相似的状态,但其实际数据不同。如果每个对象都保留了完整的状态信息,将会占用大量的内存。而享元模式的目标就是通过共享对象的内部状态来最大程度地减少内存占用。
享元模式的核心思想是将对象分为两个部分:内部状态(Intrinsic State)和外部状态(Extrinsic State)。
1、内部状态是对象共享的部分,并且独立于对象的场景,通常可以被多个对象共享。
2、外部状态则是对象特定的、不可共享的部分,它会随着场景的改变而改变。
享元模式的关键是引入一个工厂类或享元池(Flyweight Pool),负责创建和管理共享的对象。当需要获取对象时,首先在享元池中查找是否存在符合条件的对象,如果存在则直接返回;如果不存在,则创建一个新的对象并将其添加到享元池中,以便下次可以复用。
享元模式运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
● Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
● ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
● UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
● FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。
示例
假设我们在使用窗体时,需要蓝色和红色两种边框的窗体,那么不使用享元模式的话,每次打开一个窗体就需要创建一个新的类,如果生成的类太多则会占用太多的内存,那么我们使用享元模式来编写的话,只需要创建两种窗体,在使用的时候只需要从享元池拿即可。
普通享元模式
/**
* 抽象享元类
* */
abstract class Window {
public abstract String getColor();
public void display() {
System.out.println("窗体颜色:" + this.getColor());
}
}
//蓝色窗体类:具体享元类
class BlueWindow extends Window {
@Override
public String getColor() {
return "蓝色";
}
}
//红色窗体类:具体享元类
class RedWindow extends Window {
@Override
public String getColor() {
return "红色";
}
}
//享元工厂类:使用单例模式
class WindowFactort {
private static WindowFactort windowFactort = new WindowFactort();
private static Hashtable ht; //使用Hashtable来存储享元对象,充当享元池
//单例模式定义私有构造方法
private WindowFactort() {
ht = new Hashtable();
Window blue = new BlueWindow();
Window red = new RedWindow();
ht.put("blue",blue);
ht.put("red",red);
}
//单例模式返回唯一的工厂实例对象
public static WindowFactort getWindowFactort() {
return windowFactort;
}
//单例模式根据key返回唯一Hashtable中的元素
public Window getWindow(String key) {
return (Window) ht.get(key);
}
}
客户端
WindowFactort windowFactort = WindowFactort.getWindowFactort();
Window blue1,blue2,red1,red2;
blue1 = windowFactort.getWindow("blue");
blue2 = windowFactort.getWindow("blue");
red1 = windowFactort.getWindow("red");
red2 = windowFactort.getWindow("red");
blue1.display();
blue2.display();
System.out.println(blue1 == blue2);
red1.display();
red2.display();
输出结果
蓝色
蓝色
true
红色
红色
带外部状态的享元模式
实现了普通享元模式后我们发现,虽然每个窗体都享有同一个地址,但是并没有什么区分,比如每个窗体的位置大小,因此,不仅要具有相同的部分,也需要有不同的部分,从此例子来看,颜色就是相同的部分,位置及大小就是不同的部分。此处的位置及大小就是享元模式下的外部状态,颜色则为内部状态
享元模式外部状态类定义
/**
* 享元模式的外部状态类
* */
public class PositionWindow {
String position;
public PositionWindow(String position) {
this.position = position;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
}
修改享元抽象类
/**
* 抽象享元类
* */
abstract class Window {
public abstract String getColor();
public void display(PositionWindow positionWindow) {
System.out.println("窗体位置:" + positionWindow.getPosition() + ";窗体颜色:" + this.getColor());
}
}
客户端测试
WindowFactort windowFactort = WindowFactort.getWindowFactort();
Window blue1,blue2,red1,red2;
blue1 = windowFactort.getWindow("blue");
blue2 = windowFactort.getWindow("blue");
red1 = windowFactort.getWindow("red");
red2 = windowFactort.getWindow("red");
blue1.display(new PositionWindow("top"));
blue2.display(new PositionWindow("left"));
System.out.println(blue1 == blue2);
red1.display(new PositionWindow("bottom"));
red2.display(new PositionWindow("right"));
输出结果
窗体位置:top;窗体颜色:蓝色
窗体位置:left;窗体颜色:蓝色
true
窗体位置:bottom;窗体颜色:红色
窗体位置:right;窗体颜色:红色
单纯享元模式和复合享元模式
单纯享元模式(Pure Flyweight Pattern)和复合享元模式(Composite Flyweight Pattern)是两种不同的变体,都属于享元模式的扩展。
单纯享元模式(Pure Flyweight Pattern):单纯享元模式将共享对象的内部状态进行共享,对于外部状态不进行共享,外部状态需要在使用时通过参数传入。这样可以减少内存消耗,但使用时需要管理和传递外部状态。单纯享元模式适用于外部状态相对稳定且易于获取的场景。
复合享元模式(Composite Flyweight Pattern):复合享元模式将共享对象进一步组合成更大的粒度对象,并以树状结构进行管理。每个子对象可以是单纯享元或者是其他复合享元。复合享元模式允许在享元对象之间共享内部状态和外部状态,同时支持对复合对象进行操作。复合享元模式适用于有层次结构或者多维度关联的场景。
享元模式可以结合组合模式示例
首先,定义享元接口 Flyweight,它包含了设置外部状态的方法 setExternalState() 和执行操作的方法 operate():
public interface Flyweight {
void setExternalState(String state);
void operate();
}
然后,实现具体的享元对象 ConcreteFlyweight,它包含一个内部状态 internalState 和一个外部状态 externalState:
public class ConcreteFlyweight implements Flyweight {
private String internalState;
private String externalState;
public ConcreteFlyweight(String internalState) {
this.internalState = internalState;
}
public void setExternalState(String state) {
this.externalState = state;
}
public void operate() {
System.out.println("Internal State: " + internalState);
System.out.println("External State: " + externalState);
}
}
接下来,定义复合享元对象 CompositeFlyweight,它可以包含多个具体享元对象,并提供方法来设置它们的外部状态:
import java.util.ArrayList;
import java.util.List;
public class CompositeFlyweight implements Flyweight {
private List<Flyweight> flyweights = new ArrayList<>();
public void addFlyweight(Flyweight flyweight) {
flyweights.add(flyweight);
}
public void setExternalState(String state) {
for (Flyweight flyweight : flyweights) {
flyweight.setExternalState(state);
}
}
public void operate() {
for (Flyweight flyweight : flyweights) {
flyweight.operate();
}
}
}
最后,可以使用复合享元模式来统一设置多个享元对象的外部状态:
public class Client {
public static void main(String[] args) {
ConcreteFlyweight flyweight1 = new ConcreteFlyweight("A");
ConcreteFlyweight flyweight2 = new ConcreteFlyweight("B");
ConcreteFlyweight flyweight3 = new ConcreteFlyweight("C");
CompositeFlyweight compositeFlyweight = new CompositeFlyweight();
compositeFlyweight.addFlyweight(flyweight1);
compositeFlyweight.addFlyweight(flyweight2);
compositeFlyweight.addFlyweight(flyweight3);
String externalState = "External State";
compositeFlyweight.setExternalState(externalState);
compositeFlyweight.operate();
}
}
两者的区别主要在于对外部状态的处理方式。单纯享元模式将外部状态作为方法参数传入,每个享元对象独立管理自己的外部状态;而复合享元模式将外部状态作为共享状态,被所有相关享元对象共享并协同管理。
总之,单纯享元模式和复合享元模式都是为了优化共享对象的内存占用,并提高系统性能。选择使用哪种方式取决于外部状态的特点以及对象之间的关系。
与其他模式的联用
享元模式通常需要和其他模式一起联用,几种常见的联用方式如下:
1、在享元模式的享元工厂类中通常提供一个静态的工厂方法用于返回享元对象,使用简单工厂模式来生成享元对象。
2、在一个系统中,通常只有唯一一个享元工厂,因此可以使用单例模式进行享元工厂类的设计。
3、享元模式可以结合组合模式形成复合享元模式,统一对多个享元对象设置外部状态。
总结
优点:
1、减少内存占用:享元模式通过共享相似对象的内部状态来减少系统中的内存占用。相同的内部状态可以被多个对象共享,避免了每个对象都保存一份相同的数据。
2、提高性能:由于共享对象的复用,减少了对象的创建和销毁次数,从而提高了系统的性能。
3、简化对象结构:享元模式将对象分为内部状态和外部状态,内部状态可以共享,外部状态可以在运行时传入。这样简化了对象的结构,使得系统更加灵活、可扩展和易于维护。
4、支持大规模对象共享:享元模式可以在面对大量细粒度对象时发挥作用,可以有效地管理和共享大规模对象,提高系统的整体性能。
缺点:
1、引入共享状态会增加系统的复杂性:享元模式需要将对象的状态分为内部状态和外部状态,并为共享状态的一致性管理引入了额外的复杂性,增加了系统设计和维护的难度。
2、共享对象的线程安全问题:如果多个线程同时访问共享对象并修改其外部状态,可能需要额外的同步机制来保证线程安全,这会增加开发的复杂性。
适用范围:
1、当一个系统中存在大量相似对象,并且这些对象可以区分为内部状态和外部状态时,可以考虑使用享元模式。
2、当需要缓存、共享或复用对象以提高性能和降低内存占用时,可以采用享元模式。
3、当对象的大部分状态可以转化为内部状态,而少量状态可以作为外部状态在运行时传入时,适合使用享元模式。
总结:享元模式适用于需要处理大量细粒度对象且可共享内部状态的情况。它可以减少内存占用和提高性能,但同时也增加了系统的复杂性。在具体应用时,需要根据具体情况权衡利弊,确保享元模式能够带来实际的性能提升和资源节约。