liuxh-cn

概念

享元模式,是以共享的方式,对大量“细粒度对象”重用,来减少内存的使用(大量重复地创建、销毁对象)。

Flyweight,搏击比赛中体重级别之一,中文称为“蝇量级”或“次最轻量级”。把这个单词移植到软件工程中,也是用来表示特别小的对象,即细粒度对象。由此可见,享元模式的特点不仅是“共享”,更是“细粒度”的共享。

享元类中可以共享的“相同的”内容称为 内部状态 (Intrinsic State),需要外部环境设置的特异内容称为 外部状态 (extrinsic State),二者相互独立,彼此解耦。由于区分了“内部状态”和“外部状态”,因此可以通过设置不同的外部状态使得「相同的对象具有一些不同的特征」,而相同的内部状态使可以共享的。所以,享元模式的本质是「解耦变与不变,并共享不变」,来减少对象数量并节约内存。将完整的对象解耦成“变”与“不变”,即分解成更细的粒度。

享元模式借鉴“单例模式”的共享,借鉴“工厂模式”的统一供应,其特点是对“细粒度对象”重用。
与单例模式的区别在于,细粒度重用,而不是把完整的对象重用。
使用享元工厂来维护一个“享元池”。

角色 & UML

抽象享元类
具体享元类
享元工厂类
客户端

Demo: 编辑器图片重用

享元模式在编辑器软件中大量使用,如在一个文档中多次出现相同的图片,则只需要创建一个图片对象,通过在应用程序中设置该图片出现的位置,来实现该图片在不同地方多次重复显示。

UML:

代码结构:

./
├── Client.java
├── ImageNodeFactory.java
├── flyweight
│   ├── BirdImage.java
│   ├── ImageNode.java
│   ├── TreeImage.java
│   ├── bird.jpeg
│   └── tree.jpg
└── result.pdf

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

  1. https://www.jianshu.com/p/f9997c0c2a55
  2. 享元模式-连连看的图片共享 https://blog.csdn.net/ABAP_Brave/article/details/78528416
  3. 结合JDK源码看设计模式——享元模式

相关文章: