概念
享元模式(Flyweight Pattern),是以 共享 的方式,对 大量细粒度对象 重用,来减少内存的使用(避免大量重复地创建、销毁对象)。
名称中的Flyweight,是搏击比赛中体重级别之一,中文称为 蝇量级 或 次最轻量级 。把这个单词移植到软件工程中,也是用来表示特别小的对象,即细粒度对象。由此可见,享元模式的特点不仅是 共享,更是强调 细粒度的共享。
内部状态、外部状态、享元池
享元类中可以共享的相同的内容称为 内部状态(Intrinsic State),需要外部环境设置的特异内容称为 外部状态(Extrinsic State),二者相互独立,彼此解耦,组合在一起共同构成了享元类。不同但是有关系的(比如同一类的)享元对象聚合在一起构成了一个享元池,使用一个享元工厂来维护这个享元池。
这样在使用的时候,根据需求,调用享元工厂(在其维护的享元池中)获取需要的享元对象,然后对其配置外部状态,来使用。
由于区分了 内部状态 和 外部状态,因此可以通过设置不同的外部状态使得相同的对象具有一些不同的特征,而相同的内部状态使可以共享的。所以,享元模式的本质是 将完整的对象分解出更细的粒度,来解耦变与不变,并共享不变,实现减少对象数量并节约内存的目的。
享元模式借鉴单例模式的共享,借鉴工厂模式的统一供应,其特点是对细粒度对象重用。
与单例模式的区别在于,细粒度重用,而不是把完整的对象重用。
角色 & UML
抽象享元类
具体享元类
享元工厂类
客户端
Demo: 编辑器图片重用 - Java
需求:
享元模式在编辑器软件中大量使用,如在一个文档中多次出现相同的图片,则只需要创建一个图片对象,通过在应用程序中设置该图片出现的位置,来实现该图片在不同地方多次重复显示。
UML:
代码结构:
./
├── Client.java
├── ImageNodeFactory.java
├── flyweight
│ ├── BirdImage.java
│ ├── ImageNode.java
│ ├── TreeImage.java
│ ├── bird.jpeg
│ └── tree.jpg
└── result.pdf
在这里,PDF编辑使用itextpdf包,pom.xml中配置
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
ImageNode <Interface>: 抽象享元类
package homework.Flyweight.flyweight;
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.Image;
import java.io.IOException;
public interface ImageNode {
// 返回包装了坐标(外部状态 << 参数)的图片(内部状态)对象
public Image getImageInPdf(int x, int y) throws IOException, BadElementException;
}
BirdImage <Class>: 具体享元类
package homework.Flyweight.flyweight;
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.Image;
import javax.imageio.ImageIO;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
public class BirdImage implements ImageNode {
// 享元维护的内部状态(图片,共享)
private byte[] image;
// 加载内部状态(图片)
public BirdImage() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(
ImageIO.read(new File("src/main/java/homework/Flyweight/flyweight/bird.jpeg")),
"jpeg",
baos);
this.image = baos.toByteArray();
}
@Override
public Image getImageInPdf(int x, int y) throws IOException, BadElementException {
Image imageInPdf = Image.getInstance(this.image);
// 内部状态(图片对象) + 外部状态(图片位置)
imageInPdf.scaleAbsolute(30, 30);
imageInPdf.setAbsolutePosition(x, y);
// 打印图片在内存中的地址
System.out.println(this.image);
return imageInPdf;
}
}
TreeImage <Class>: 具体享元类
package homework.Flyweight.flyweight;
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.Image;
import javax.imageio.ImageIO;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
public class TreeImage implements ImageNode {
// 享元维护的内部状态(图片,共享)
private byte[] image;
// 加载内部状态(图片)
public TreeImage() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(
ImageIO.read(new File("src/main/java/homework/Flyweight/flyweight/tree.jpg")),
"jpg",
baos);
this.image = baos.toByteArray();
}
@Override
public Image getImageInPdf(int x, int y) throws IOException, BadElementException {
Image imageInPdf = Image.getInstance(image);
// 内部状态(图片对象) + 外部状态(图片位置)
imageInPdf.scaleAbsolute(30, 30);
imageInPdf.setAbsolutePosition(x, y);
// 打印图片在内存中的地址
System.out.println(this.image);
return imageInPdf;
}
}
ImageNodeFactory <Class>: 享元工厂
package homework.Flyweight;
import homework.Flyweight.flyweight.BirdImage;
import homework.Flyweight.flyweight.ImageNode;
import homework.Flyweight.flyweight.TreeImage;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ImageNodeFactory {
// 享元工厂维护的享元池
private static Map<String, ImageNode> imagePool = new ConcurrentHashMap<>();
// 获取享元
public static ImageNode getImageNode(String type) throws IOException {
if(!imagePool.containsKey(type)) {
switch (type) {
case "bird":
imagePool.put(type, new BirdImage());
break;
case "tree":
imagePool.put(type, new TreeImage());
break;
}
}
return imagePool.get(type);
}
}
Client <Class>: 客户端
package homework.Flyweight;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfWriter;
import homework.Flyweight.flyweight.ImageNode;
import java.io.*;
public class Client {
public static void main(String[] args) throws IOException, DocumentException {
// 1. 初始化PDF
Rectangle rectangle = new Rectangle(PageSize.A4); // 设置 PDF 纸张矩形,大小采用 A4
rectangle.setBackgroundColor(BaseColor.WHITE); // 设置背景色
// 创建一个文档对象,设置初始化大小和页边距
Document document = new Document(rectangle, 10, 10, 10, 10); // 上、下、左、右页间距
String pdfPath = "src/main/java/homework/Flyweight/result.pdf"; // PDF 的输出位置
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(pdfPath));
document.open(); // 打开文档对象
// 2. PDF中插入图片
for(int i=0; i<10; i++){
ImageNode imageNode = ImageNodeFactory.getImageNode("tree");
// 图片坐标(外部状态)由客户端维护
document.add(imageNode.getImageInPdf(10*i,100*i));
System.out.println("Object ImageNode: "+imageNode);
}
document.close(); // 关闭文档
}
}
控制台打印:
[B@16b4a017
Object ImageNode: homework.Flyweight.flyweight.TreeImage@5ecddf8f
[B@16b4a017
Object ImageNode: homework.Flyweight.flyweight.TreeImage@5ecddf8f
[B@16b4a017
Object ImageNode: homework.Flyweight.flyweight.TreeImage@5ecddf8f
[B@16b4a017
Object ImageNode: homework.Flyweight.flyweight.TreeImage@5ecddf8f
[B@16b4a017
Object ImageNode: homework.Flyweight.flyweight.TreeImage@5ecddf8f
[B@16b4a017
Object ImageNode: homework.Flyweight.flyweight.TreeImage@5ecddf8f
[B@16b4a017
Object ImageNode: homework.Flyweight.flyweight.TreeImage@5ecddf8f
[B@16b4a017
Object ImageNode: homework.Flyweight.flyweight.TreeImage@5ecddf8f
[B@16b4a017
Object ImageNode: homework.Flyweight.flyweight.TreeImage@5ecddf8f
[B@16b4a017
Object ImageNode: homework.Flyweight.flyweight.TreeImage@5ecddf8f
Process finished with exit code 0
生成的PDF:
Reference
- https://www.jianshu.com/p/f9997c0c2a55
- 享元模式-连连看的图片共享 https://blog.csdn.net/ABAP_Brave/article/details/78528416
- 结合JDK源码看设计模式——享元模式
来源:oschina
链接:https://my.oschina.net/u/4416282/blog/4288643