【设计模式-11】享元模式的代码实现及使用场景

 享元模式,一种使用概率比较低的设计模式,享元表述的是共享元数据的意思,使用的初衷是为了节约内存空间,并在一定程度上可以提高系统性能。

 我们来举个电商中的场景,某个大学在为学生宿舍安装购置了5000台空调。在代码中实现,需要在内存中创建5000个空调对象,对象中包含空调的内机信息、外机信息、用户的信息、说明书信息、厂商的信息等等,属于一个大对象。而对象的实例化是个很复杂、消耗资源的操作,所以通过new来操作必然是很消耗性能的。这个时候可以借助原型模式来实现,可以在很大程度上减少实例化的次数,确实可以提效,但是依然在内存中需要保留这5000个大对象,仍然无法解决资源占用的问题。这个时候,享元模式就可以解决这个问题。

1. 概述

享元模式 运用共享技术实现大量相似对象的复用。系统可以只创建少量的对象,对象中某些属性是相同的不变的,这些不变的属性称为内部状态,同时依靠客户端传入一些外部属性,来区分不同的对象,这些变化的属性成为外部状态。享元模式是一种结构性设计模式。

 享元模式的原理很简单,其实就是在堆中创建少量对象,通过改变对象的内部属性,而不改变对象的内存地址,实现对象的复用。

 在享元模式中,存储这些共享实例的地方称为享元池,比如26个英文字母在内存中会被无数次使用到,这个时候每个字母就可以记录到特定享元池中。享元模式通常和工厂模式搭配使用,享元池就存储在工厂模式中。

在这里插入图片描述
 享元模式的实现需要3个角色:

  • 抽象享元类:是一个接口或者抽象类,在抽象享元类中声明了公共方法,这些方法可以向外界提供对象的内部状态数据,也可以通过这些方法来设置外部状态。
  • 享元实现类:抽象享元类的实现或者子类,维护自身的内部状态,可以结合单例模式来设计具体享元类,以此来为每个具体享元类提供唯一的享元对象。
  • 享元工厂类:用来维护享元对象的工厂类,负责对享元对象实例的创建与管理,并且可以给外部提供获取享元对象的服务。

2. 代码实现

 在下面这个代码案例中,我们来写一个下围棋的Demo,其中白色棋子的位置从[0, 1] 到 [179, 180],黑色棋子的位置从[180, 181]到[359, 360],实现如下:

  • 抽象享元类
// 享元接口 - 围棋棋子
public abstract class GoChess {
    // 围棋颜色(内部状态)
    private String color;
    // 围棋材质(内部状态)
    private String texture;
    // 围棋尺寸(内部状态)
    private String size;
    // 坐标位置(外部状态)
    private int x, y;

    GoChess(String color) {
        this.color = color;
        this.texture = "大理石";
        this.size = "半径1cm";
    }

    // 设置位置坐标
    public void setCoordinate(int x, int y) {
        System.out.println(String.format("【%s】的坐标位置是[%s, %s]", color, x, y));
    }
}
  • 具体享元实现
// 具体享元实现-白色围棋棋子
public class WhiteGoChess extends GoChess{
    // 设置单例模式
    private static volatile WhiteGoChess instance;
    // 设置围棋的颜色
    private WhiteGoChess(String color) {
        super(color);
    }
    public static WhiteGoChess getInstance() {
        if (instance == null) {
            synchronized (WhiteGoChess.class) {
                if (instance == null) {
                    instance = new WhiteGoChess("白色");
                }
            }
        }
        return instance;
    }
}

// 具体享元实现 - 黑色围棋棋子
public class BlackGoChess extends GoChess {
    // 设置单例模式
    private static volatile BlackGoChess instance;
    // 设置围棋的颜色
    private BlackGoChess(String color) {
        super(color);
    }
    // 获取享元对象的方法
    public static BlackGoChess getInstance() {
        if (instance == null) {
            synchronized (WhiteGoChess.class) {
                if (instance == null) {
                    instance = new BlackGoChess("黑色");
                }
            }
        }
        return instance;
    }
}
  • 享元工厂类
// 享元对象工厂 - 这里可以设置为单例模式
public class GoChessFactory {

    public GoChessFactory() {}
    // 静态内部类,用作单例实现
    private static class Pool {
        static final Map<String, GoChess> INSTANCE = new HashMap<>();
        // 静态代码块中复制
        static {
            INSTANCE.put("白色", WhiteGoChess.getInstance());
            INSTANCE.put("黑色", BlackGoChess.getInstance());
        }
    }
    // 获取享元对象的方法
    public static GoChess getInstance(String color) {
        // 享元池中获取
        return Pool.INSTANCE.get(color);
    }
}
  • 客户端
// 客户端的实现
public class Client {

    public static void main(String[] args) {
        // 设置180个白棋
        for (int i = 0; i < 180; i++) {
            GoChessFactory.getInstance("白色").setCoordinate(i, i + 1);
        }
        // 设置180个黑色黑棋
        for (int i = 180; i < 360; i++) {
            GoChessFactory.getInstance("黑色").setCoordinate(i, i + 1);
        }
    }
}

 执行结果,部分结果打印如下:

在这里插入图片描述

3. UML类图

 基于我们的代码示例,画一个UML类图,如下所示:

在这里插入图片描述

4. 总结

 享元模式在开发中实际使用频率并不多,但是作为一种节约内存的设计模式,还是有一些应用场景的。

  • 首先可以极大的减少内存中对象的竖向,从而可以节约资源,提高系统性能。
  • 享元模式使用时需要注意外部状态的变化,尤其是多线程场景下注意并发的问题。
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值