• 设计模式-09-享元模式


           经典的设计模式有23种,但是常用的设计模式一般情况下不会到一半,我们就针对一些常用的设计模式进行一些详细的讲解和分析,方便大家更加容易理解和使用设计模式。

    1-​享元模式原理

           所谓“享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象

           具体来讲,当一个系统中存在大量重复对象的时候,如果这些重复的对象是不可变对象,我们就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。这样可以减少内存中对象的数量,起到节省内存的目的。实际上,不仅仅相同对象可以设计成享元,对于相似对象,我们也可以将这些对象中相同的部分(字段)提取出来,设计成享元,让这些大量相似对象引用这些享元。

          “不可变对象”指的是,一旦通过构造函数初始化完成之后,它的状态(对象的成员变量或者属性)就不会再被修改了。所以,不可变对象不能暴露任何set()等修改内部状态的方法。之所以要求享元是不可变对象,那是因为它会被多处代码共享使用,避免一处代码对享元进行了修改,影响到其他使用它的代码。

    2-享元模式代码示例

           需求描述:游戏大厅下象棋,一个游戏厅中有成千上万个“房间”,每个房间对应一个棋局。棋局要保存每个棋子的数据,比如:棋子类型(将、相、士、炮等)、棋子颜色(红方、黑方)、棋子在棋局中的位置。利用这些数据,我们就能显示一个完整的棋盘给玩家。为了记录每个房间当前的棋局情况,我们需要给每个房间都创建一个棋局对象ChessBoard(成员变量:id、text、color、positionX、positionY)。因为游戏大厅中有成千上万的房间(实际上,百万人同时在线的游戏大厅也有很多),那保存这么多棋局对象就会消耗大量的内存。有没有什么办法来节省内存呢?

           如果每个将id、text、color、positionX、positionY设计在一个类中,每次都new一个对象,在内存中会有大量的相似对象 ,相似对象的id、text、color都是相同的(是有限的少的),唯独positionX、positionY不同。实际上,我们可以将棋子的id、text、color属性拆分出来,设计成独立的类,并且作为享元供多个棋盘复用。这样,棋盘只需要记录每个棋子的位置信息就可以了。

    1. // 享元类
    2. public class ChessPieceUnit {
    3. private int id;
    4. private String text;
    5. private Color color;
    6. public ChessPieceUnit(int id, String text, Color color) {
    7. this.id = id;
    8. this.text = text;
    9. this.color = color;
    10. }
    11. public static enum Color {
    12. RED, BLACK
    13. }
    14. // ...省略其他属性和getter方法...
    15. }
    16. public class ChessPieceUnitFactory {
    17. private static final Map pieces = new HashMap<>();
    18. static {
    19. pieces.put(1, new ChessPieceUnit(1, "車", ChessPieceUnit.Color.BLACK));
    20. pieces.put(2, new ChessPieceUnit(2,"馬", ChessPieceUnit.Color.BLACK));
    21. //...省略摆放其他棋子的代码...
    22. }
    23. public static ChessPieceUnit getChessPiece(int chessPieceId) {
    24. return pieces.get(chessPieceId);
    25. }
    26. }
    27. public class ChessPiece {
    28. private ChessPieceUnit chessPieceUnit;
    29. private int positionX;
    30. private int positionY;
    31. public ChessPiece(ChessPieceUnit unit, int positionX, int positionY) {
    32. this.chessPieceUnit = chessPieceUnit;
    33. this.positionX = positionX;
    34. this.positionY = positionY;
    35. }
    36. // 省略getter、setter方法
    37. }
    38. public class ChessBoard {
    39. private Map chessPieces = new HashMap<>();
    40. public ChessBoard() {
    41. init();
    42. }
    43. private void init() {
    44. chessPieces.put(1, new ChessPiece(
    45. ChessPieceUnitFactory.getChessPiece(1), 0,0));
    46. chessPieces.put(1, new ChessPiece(
    47. ChessPieceUnitFactory.getChessPiece(2), 1,0));
    48. //...省略摆放其他棋子的代码...
    49. }
    50. public void move(int chessPieceId, int toPositionX, int toPositionY) {
    51. //...省略...
    52. }
    53. }

           我们利用工厂类来缓存ChessPieceUnit信息(也就是id、text、color)。通过工厂类获取到的ChessPieceUnit就是享元。所有的ChessBoard对象共享这30个ChessPieceUnit对象(因为象棋中只有30个棋子)。在使用享元模式之前,记录1万个棋局,我们要创建30万(30*1万)个棋子的ChessPieceUnit对象。利用享元模式,我们只需要创建30个享元对象供所有棋局共享使用即可,大大节省了内存。

    3-享元模式在Integer和String中的应用

           Integer用到了享元模式来复用对象。当我们通过自动装箱,也就是调用valueOf()来创建Integer对象的时候,如果要创建的Integer对象的值在-128到127之间,会从IntegerCache类中直接返回,否则才调用new方法创建。看代码更加清晰一些,Integer类的valueOf()函数的具体代码如下所示:

    1. public static Integer valueOf(int i) {
    2. if (i >= IntegerCache.low && i <= IntegerCache.high)
    3. return IntegerCache.cache[i + (-IntegerCache.low)];
    4. return new Integer(i);
    5. }

    实际上,这里的IntegerCache相当于,我们上一节课中讲的生成享元对象的工厂类,只不过名字不叫xxxFactory而已。这个类是Integer的内部类。

          String类的享元模式的设计,跟Integer类稍微有些不同。Integer类中要共享的对象,是在类加载的时候,就集中一次性创建好的。但是,对于字符串来说,我们没法事先知道要共享哪些字符串常量,所以没办法事先创建好,只能在某个字符串常量第一次被用到的时候,存储到常量池中,当之后再用到的时候,直接引用常量池中已经存在的即可,就不需要再重新创建了。

    4-享元模式vs单例、缓存、对象池

           在单例模式中,一个类只能创建一个对象,而在享元模式中,一个类可以创建多个对象,每个对象被多处代码引用共享。实际上,享元模式有点类似于之前讲到的单例的变体:多例。应用享元模式是为了对象复用,节省内存,而应用多例模式是为了限制对象的个数。

           在享元模式的实现中,我们通过工厂类来“缓存”已经创建好的对象。这里的“缓存”实际上是“存储”的意思,跟我们平时所说的“数据库缓存”“CPU缓存”“MemCache缓存”是两回事。我们平时所讲的缓存,主要是为了提高访问效率,而非复用。

          池化技术中的“复用”可以理解为“重复使用”,主要目的是节省时间(比如从数据库池中取一个连接,不需要重新创建)。在任意时刻,每一个对象、连接、线程,并不会被多处使用,而是被一个使用者独占,当使用完成之后,放回到池中,再由其他使用者重复利用。享元模式中的“复用”可以理解为“共享使用”,在整个生命周期中,都是被所有使用者共享的,主要目的是节省空间。

  • 相关阅读:
    华为交换机ssh远程登录配置命令
    惠普1020打印机查看已打印页数
    li标签去掉点和占位符
    CentOS系统环境搭建(二十)——CentOS7安装chat GPT进阶
    快速上手Linux核心命令(八):网络相关命令
    重温数据结构与算法之并查集
    【WSN定位】基于chan、taylor算法实现移动基站无源定位附matlab代码
    MySQL学习大纲
    设计模式-行为型模式-中介者模式
    Springboot+RabbitMQ实现订单超时未支付,取消订单(实战+源码下载)
  • 原文地址:https://blog.csdn.net/ycmy2017/article/details/134440712