设计模式 - 享元模式
实例
文档编辑器的设计
假设一个多功能文档编辑器的应用场景,在文档中可以插入图片、动画、视频等多媒体资料,为了节约系统资源,相同的图片、动画和视频在同一个文档中只需保存一份,但是可以多次重复出现,而且它们每次出现时位置和大小均可不同
解决方案
如果将每个文件都以单独的对象来表示,将会占用较多的内存空间,在设计模式中,享元模式提供了减少对象数量从而改善应用所需的对象结构的方式
享元模式
概念
- 享元模式(
Flyweight Pattern
):运用共享技术实现相同或相似对象的重用 - 一句话概述:减少创建对象的数量,从而减少内存的占用,并且提高性能
- 享元模式是一种对象结构型模式。
- 享元模式结构图(来自刘伟老师技术博客)
角色定义
角色 | 名称 | 释义 |
---|---|---|
Flyweight | 抽象享元类 | 通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态) |
ConcreteFlyweight | 具体享元类 | 它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象 |
UnsharedConcreteFlyweight | 非共享具体享元类 | 并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建 |
FlyweightFactory | 享元工厂类 | 享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中 |
典型代码
- 享元工厂类
class FlyweightFactory {
//定义一个HashMap用于存储享元对象,实现享元池
private HashMap flyweights = newHashMap();
public Flyweight getFlyweight(String key){
//如果对象存在,则直接从享元池获取
if(flyweights.containsKey(key)){
return(Flyweight)flyweights.get(key);
}
//如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
else {
Flyweight fw = newConcreteFlyweight();
flyweights.put(key,fw);
return fw;
}
}
}
- 享元类
class Flyweight {
//内部状态intrinsicState作为成员变量,同一个享元对象其内部状态是一致的
private String intrinsicState;
public Flyweight(String intrinsicState) {
this.intrinsicState=intrinsicState;
}
//外部状态extrinsicState在使用时由外部设置,不保存在享元对象中,
//即使是同一个对象,在每一次调用时也可以传入不同的外部状态
public void operation(String extrinsicState) {
......
}
}
具体实现
File.java
/**
* @Description 文件抽象类: 抽象享元类
*/
public abstract class File {
private String key;
private String type;
public File(String key, String type) {
this.key = key;
this.type = type;
}
public abstract String displayType();
// 省略get、set、toString
}
PictureFile.java
/**
* @Description 图片文件类:具体享元类
*/
public class PictureFile extends File {
public PictureFile(String key) {
super(key, "Picture");
}
@Override
public String displayType() {
return "图片";
}
}
VideoFile.java
/**
* @Description 视频文件类:具体享元类
*/
public class VideoFile extends File {
public VideoFile(String key) {
super(key, "Video");
}
@Override
public String displayType() {
return "视频";
}
}
CartoonFile.java
/**
* @Description 动画文件类:具体享元类
*/
public class CartoonFile extends File {
public CartoonFile(String key) {
super(key, "Cartoon");
}
@Override
public String displayType() {
return "动画";
}
}
FileFactory.java
/**
* @Description 文件工厂类:享元工厂类
*/
public class FileFactory {
private static final Map<String, File> FILE_MAP = new HashMap<>();
/**
* 通过key来获取存储在HashMap中的享元对象
*/
public static File getFile(String key, String type) {
File file = FILE_MAP.get(key);
if (file == null) {
if ("Picture".equalsIgnoreCase(type)) {
file = new PictureFile(key);
} else if ("Video".equalsIgnoreCase(type)) {
file = new VideoFile(key);
} else if ("Cartoon".equalsIgnoreCase(type)) {
file = new CartoonFile(key);
}
FILE_MAP.put(file.getKey(), file);
}
return file;
}
/**
* 打印
*/
public static void display() {
FILE_MAP.forEach((key, value)-> {
System.out.println("key:" + key + ", value:" + value);
});
}
}
EditorFile.java
/**
* @Description 编辑器文件
*/
public class EditorFile {
private final File file;
private final Attributes attributes;
public EditorFile(File file, Attributes attributes) {
this.file = file;
this.attributes = attributes;
}
@Override
public String toString() {
return "EditorFile{" +
"abstractFile=" + file +
", attributes=" + attributes +
'}';
}
}
Attributes.java
/**
* @Description 属性类:外部状态类
*/
public class Attributes {
/**
* 位置, 行
*/
private int line;
/**
* 大小
*/
private int size;
public Attributes(int line, int size) {
this.line = line;
this.size = size;
}
}
DocumentEditor.java
/**
* @Description 文档编辑器
*/
public class DocumentEditor {
List<EditorFile> editorFileList = new ArrayList<>();
/**
* 插入文件
* @param file 文件
* @param attributes 属性
*/
public void insertFile(File file, Attributes attributes) {
EditorFile editorFile = new EditorFile(FileFactory.getFile(file.getKey(), file.getType()), attributes);
editorFileList.add(editorFile);
}
/**
* 文档打印
*/
public void display() {
System.out.println("文档编辑器:");
editorFileList.forEach(System.out::println);
System.out.println("\n享元工厂:");
FileFactory.display();
}
}
Test.java
/**
* @Description 享元模式测试类
*/
public class Test {
public static void main(String[] args) {
DocumentEditor documentEditor = new DocumentEditor();
// 在各位置插入同一张图片,且大小不同
File pictureFile = new PictureFile("123");
documentEditor.insertFile(pictureFile, new Attributes(10, 100));
documentEditor.insertFile(pictureFile, new Attributes(20, 200));
documentEditor.insertFile(pictureFile, new Attributes(30, 300));
// 插入另一张图片
pictureFile.setKey("456");
documentEditor.insertFile(pictureFile, new Attributes(40, 100));
// 插入视频
File video = new VideoFile("789");
documentEditor.insertFile(video, new Attributes(50, 100));
// 文档打印
documentEditor.display();
}
}
- 输出如下:
文档编辑器:
EditorFile{abstractFile=File{key='123', type='图片'}, attributes=Attributes{line=10, size=100}}
EditorFile{abstractFile=File{key='123', type='图片'}, attributes=Attributes{line=20, size=200}}
EditorFile{abstractFile=File{key='123', type='图片'}, attributes=Attributes{line=30, size=300}}
EditorFile{abstractFile=File{key='456', type='图片'}, attributes=Attributes{line=40, size=100}}
EditorFile{abstractFile=File{key='789', type='视频'}, attributes=Attributes{line=50, size=100}}
享元工厂:
key:123, value:File{key='123', type='图片'}
key:456, value:File{key='456', type='图片'}
key:789, value:File{key='789', type='视频'}
- 类图如下:
总结
- 优点
1.减少对象的创建,降低内存中对象的数量,降低系统的内存,提高效率
2.减少内存之外的其它资源占用
- 缺点
1.关注内/外部状态、需要关注线程安全问题
2.使系统、程序的逻辑复杂化
- 适用场景
1.常用于系统底层的开发,以便解决系统的性能问题
2.系统有大量相似对象、需要缓冲池的场景
- 相关设计模式
1.享元模式和代理模式
2.享元模式和单例模式
- 适配器模式源代码
Integer、Long(jdk)、GenericObjectPool(Tomcat)