专栏名称: 苏小林
目录
相关文章推荐
雨果网  ·  重要!TEMU新增关键词搜索功能 ·  14 小时前  
跨境电商Eason  ·  eBay是如何将一个单一的商品变成一个受欢迎 ... ·  3 天前  
跨境电商Eason  ·  eBay是如何将一个单一的商品变成一个受欢迎 ... ·  3 天前  
雨果网  ·  速度下架!大批店铺挂了 ·  5 天前  
51好读  ›  专栏  ›  苏小林

6/24 设计模式之享元模式 Flyweight Pattern

苏小林  · 掘金  ·  · 2020-03-16 02:26

正文

阅读 108

6/24 设计模式之享元模式 Flyweight Pattern

类别:结构型设计模式

目的:减少潜在的对象创建次数、尽可能延迟创建/重用对象,降低内存占用,并对代码实现结构进行限定

完整代码参考:1drv.ms/u/s!AquRvPz…

典型场景

有许多不同种类的对象需要被使用,需要延迟对象的创建、同类对象会创建多次

比如一张地图中包含很多点,每个点会有一个图标表示这个地点的类型,比如餐厅、体育馆、办公楼、图书馆等。把这些点显示在地图上,程序需要先在内存中保存这些点

基本事实:

  1. 一张地图上可能会有成千上万的点需要绘制
  2. 每个地图点在地图上一般会以图片进行显示
  3. 一个图片文件内容占用的内容空间远超普通对象
  4. 为了避免地图加载过程中占用过多内存,有必要优化地图点上的内存空间占用

一张典型的地图参考如下:

可以看到,同类型建筑的图片是一样的

这里拿5个地图点进行举例:3个餐厅+2个体育馆

硬编码

构建代表地图点和图片类型

代表地图点图片类参考如下:

public class PointImage {
    private final String type;
    private final byte[] image;

    public PointImage(String type, byte[] image) {
        this.type = type;
        this.image = image;
    }

    public String getType() {
        return type;
    }
}
复制代码

地图点类参考如下:

public class Point {
    private int longitude; // 经度
    private int latitude; // 纬度
    private PointImage image;

    public Point(int longitude, int latitude, PointImage image) {
        this.longitude = longitude;
        this.latitude = latitude;
        this.image = image;
    }

    public void display() {
        System.out.printf("image: %s, longtitude: %d, latitude %d)", image.getType(), longitude, latitude);
    }
}
复制代码

很容易写出下面的生成5个带图片的地图点的代码、参考如下:

// 三个餐厅
var pointImage1 = new PointImage("restaurant", loadBytes("restaurant.png"));
var point1 = new Point(1, 2, pointImage1);

var pointImage2 = new PointImage("restaurant", loadBytes("restaurant.png"));
var point2 = new Point(1, 2, pointImage2);

var pointImage3 = new PointImage("restaurant", loadBytes("restaurant.png"));
var point3 = new Point(1, 2, pointImage3);

// 两个体育馆
var pointImage4 = new PointImage("stadium", loadBytes("stadium.png"));
var point4 = new Point(1, 2, pointImage3);

var pointImage5 = new PointImage("stadium", loadBytes("stadium.png"));
var point5 = new Point(1, 2, pointImage3);
复制代码

从上面的代码可以看出

  1. 每个地图点包含一个图片,图片是地图点的元数据
  2. 加载了多张图片,同时一类图片可能会加载多次,造成内存浪费/或者内存溢出
  3. 每类建筑可以复用一张图片,但不同类型的建筑需要使用不同的图片

模式实现

使用一个单例对象表示一类建筑的图片,地图上存在多种类型的建筑,每个类型对应/复用一张图片,可以使用单例来表示一个图片对象,多类的建筑即会对应多个单例对象

存在多个单例可复用的对象,为了能够方便控制这些单例对象的创建,可以使用工厂设计模式来生成这些单例对象,并从在生成这些对象时进行检查,已生成过,直接返回即可

对应的单例工厂核心如下

public class PointImageFactory {
    private Map<String, PointImage> imageList = new HashMap<>();

    public PointImage getPointIcon(String type) {
        if (imageList.containsKey(type)) {
            return imageList.get(type);
        }

        PointImage pointImage = null;
        switch (type) {
            case "restaurant": // 餐厅
                // 从文件加载餐厅图片到内存
                pointImage = new PointImage(type, loadBytes("restaurant.png"));
                break;
            case "stadium": // 体育馆
                // 从文件加载体育馆图片到内存
                pointImage = new PointImage(type, loadBytes("stadium.png"));
                break;
        }
        imageList.put(type, pointImage);

        return imageList.get(type);
    }

    public static byte[] loadBytes (String ImageName) {
        // 加载图片为byte实现...
    }
}
复制代码

可以看到,同类型的图片会被缓存在imageList列表中,再次请求同类型的图片,将直接从imageList列表中获取,无需重新创建了。

在创建地图上的点时,使用这个图片工厂创建点对应的图片,参考如下:

PointImageFactory imageFactory = new PointImageFactory();

// 三个餐厅
var point1 = new Point(1, 2, imageFactory.getPointIcon("restaurant"));
var point2 = new Point(1, 2, imageFactory.getPointIcon("restaurant"
                            

                            





请到「今天看啥」查看全文