• 每日一个设计模式之【享元模式】


    每日一个设计模式之【享元模式】

    ☁️前言🎉🎉🎉

      大家好✋,我是知识汲取者😄,今天给大家带来一篇有关享元模式的学习笔记。众所周知能够熟练使用设计模式是一个优秀程序猿的必备技能,当我们在项目中选择一个或多个合适的设计模式,不仅能大大提高项目的稳健性可移植性可维护性,同时还能让你的代码更加精炼,具备艺术美感

      有时候我们在系统中由于对象创建的数量过多,会造成内存溢出,比如我们设计一个下围棋的程序,黑子有181颗、白子有180颗,总共有361颗,如果我们一股脑地不加以注意,每落一颗子就创建一个对象的化,那问题就大量,得创建大量对象。而享元模式的出现就能很好地解决这一弊端,很大程度地节约了内存,同时也能够提高程序的性能,享元模式有类似于线程池,它可以提供一个享元池,享元池中的对象可以被共享,能够被系统复用,从而大大降低对象的创建数量……话不多说,且看下文

    image-20221116203813552

    推荐阅读

    • 设计模式导学:🚪传送门
    • 每日一个设计模式系列专栏:🚪传送门
    • 设计模式专属Gitee仓库:✈启程
    • 设计模式专属Github仓库:🚀上路
    • 知识汲取者的个人主页:💓点我哦

    csdn

    🌻享元模式概述

    • 什么是享元模式

      享元模式(Flyweight Pattern)是一种结构型模式,它利用共享技术实现对象的复用,进而减少内存占用和提高程序性能

    • 享元模式的作用:减少对象的创建,减少内存占用和提高程序性能

    • 享元模式的优缺点

      • 优点

        • 提高系统性能。共享模式能够使用一个”享元池“存储提前需要创建的对象,后续需要使用对象可以直接从享元池中获取,而无需去创建,提高对象的创建速度
        • 节约内存。共享模式让对象能够共享使用,进而复用,大大节约了内存的消耗

        ……

      • 缺点

        • 具有一定局限性。享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式
        • 提高了系统的复杂度。享元模式需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱,并且还可能会引起线程安全问题
        • 过渡使用会造成不必要的内存资源浪费。享元模式中享元池的对象都是提前创建的,会占用系统的资源

        ……

    • 享元模式的引用场景

      • 系统需要创建大量粒度较小且相似的对象,可以使用享元模式

      • 系统需要一个缓冲池,可以使用享元模式进行实现

      ……

    • 享元模式的角色划分

      • 抽象享元类(Flyweight):是一个具体实体的抽象类,内部含有所有具体享元类公有的方法,可以是抽象类、接口
      • 具体享元类(ConcreteFlyweight):是一个具体类,内部存储了内部状态和外部状态(也可以将这两个状态存储在抽象享元类中),其实例化对象是享元对象
      • 非共享享元类(UnsharedConcreteFlyweight):是一个具体的类,是享元对象的外部状态
      • 享元工厂(FlyweightFactory):享元模式中的核心角色,内部含有一个享元池,用于提前创建并存储需要使用的享元对象
      • 客户端(Client):系统的外部交互者(对应本文示例中的测试类)
    • 相关概念

      • 内部状态(Intrinsic State):存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享(例如:围棋的颜色)
      • 外部状态(Extrinsic State):随环境改变而改变的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的,且不可共享(例如:围棋的坐标)

    🌱享元模式的实现

    示例

    围棋分为黑子和白子两种,所有的棋子非黑即白,所以棋子的颜色是可以被共享的,这是围棋的内部状态;所有的围棋在落子后都有一个独一无二的坐标,所以围棋的落子坐标是不可以被共享的,这是围棋的外部状态。使用享元模式实现围棋对象的创建

    image-20221116211828203

    创建抽象享元类
    创建具体享元类
    创建非共享享元类
    创建享元工厂
    编写测试类
    • Step1:创建抽象享元类

      package com.hhxy.chessman;
      
      /**
       * @author ghp
       * @date 2022/10/19
       * @title 棋子的抽象类
       * @description
       */
      public abstract class Chessman {
      
          protected String color;//围棋的颜色属性,它是棋子的内部状态,享元池中所有的棋子对象共享
          protected Coordinates coordinates;//围棋的坐标属性,它是棋子外部状态,享元池中棋子对象无法共享
      
          /**
           * 获取棋子的颜色
           */
          public abstract String getColor();
      
          /**
           * 展示棋子的颜色
           */
          public void showColor(){
              System.out.print("棋子颜色为: " + this.getColor() + "; ");
          }
      
          /**
           * 展示棋子的坐标
           */
          public void showCoordinates(Coordinates coordinates){
              this.coordinates = coordinates;
              System.out.print("棋子的坐标为: " + "(" + this.coordinates.getX() + "," + this.coordinates.getY() + ")");
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
    • Step2:创建具体享元类

      1)黑子:

      package com.hhxy.chessman;
      
      /**
       * @author ghp
       * @date 2022/10/19
       * @title
       * @description
       */
      public class BlackChessman extends Chessman{
      
          public BlackChessman() {
              this.color = "黑色";
          }
      
          /**
           * 获取棋子的颜色
           */
          @Override
          public String getColor() {
              return this.color;
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23

      2)白子:

      package com.hhxy.chessman;
      
      /**
       * @author ghp
       * @date 2022/10/19
       * @title
       * @description
       */
      public class WhiteChessman extends Chessman{
      
          public WhiteChessman() {
              this.color = "白色";
          }
      
          /**
           * 获取棋子的颜色
           */
          @Override
          public String getColor() {
              return this.color;
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
    • Step3:创建非共享享元类

      package com.hhxy.chessman;
      
      /**
       * @author ghp
       * @date 2022/10/19
       * @title 坐标类
       * @description 用于标识围棋的外部状态
       */
      public class Coordinates {
          private int x;//围棋棋子的横坐标
          private int y;//围棋棋子的纵坐标
      
          public Coordinates(int x, int y) {
              this.x = x;
              this.y = y;
          }
      
          public int getX() {
              return x;
          }
      
          public void setX(int x) {
              this.x = x;
          }
      
          public int getY() {
              return y;
          }
      
          public void setY(int y) {
              this.y = y;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
    • Step4:创建享元工厂

      package com.hhxy.factory;
      
      import com.hhxy.chessman.BlackChessman;
      import com.hhxy.chessman.Chessman;
      import com.hhxy.chessman.WhiteChessman;
      
      import java.util.HashMap;
      import java.util.Map;
      
      /**
       * @author ghp
       * @date 2022/10/19
       * @title 棋子工厂类
       * @description 内部含有一个享元池,是享元模式的核心,用于批量创建同类型且具有共同属性的对象
       */
      public class ChessmanFactory {
          private static ChessmanFactory instance;
          private static Map<String,Object> map;//享元池
      
          private ChessmanFactory(){
              //提前将棋子对象存入享元池中
              map = new HashMap<>();
              WhiteChessman whiteChessman = new WhiteChessman();
              BlackChessman blackChessman = new BlackChessman();
              map.put("whiteChessman",whiteChessman);
              map.put("blackChessman",blackChessman);
          }
      
          //使用单例模式创建享元工厂对象,提高效率的同时也能节约内存
          static{
              instance = new ChessmanFactory();
          }
      
          /**
           * 获取单例的工厂对象
           */
          public static ChessmanFactory getInstance() {
              return instance;
          }
      
          public Chessman getChessman(String chessmanName){
              return (Chessman) map.get(chessmanName);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
    • Step5:编写测试类

      package com.hhxy.test;
      
      import com.hhxy.chessman.Chessman;
      import com.hhxy.chessman.Coordinates;
      import com.hhxy.factory.ChessmanFactory;
      
      /**
       * @author ghp
       * @date 2022/10/19
       * @title 测试类
       * @description 用于测试享元模式
       */
      public class Test {
          public static void main(String[] args) {
              Chessman[] chessmen = new Chessman[4];
              //获取棋子的工厂对象
              ChessmanFactory chessmanFactory = ChessmanFactory.getInstance();
              //从享元池中获取棋子对象
              chessmen[0] = chessmanFactory.getChessman("whiteChessman");
              chessmen[1] = chessmanFactory.getChessman("whiteChessman");
              chessmen[2] = chessmanFactory.getChessman("blackChessman");
              chessmen[3] = chessmanFactory.getChessman("blackChessman");
      
              //判断创建的对象是否是同一个,输出ture则证明是同一个对象
              System.out.println("判断两个黑棋是否相同:" + (chessmen[0].hashCode() == chessmen[1].hashCode()));
              System.out.println("判断两个白棋是否相同:" + (chessmen[2].hashCode() == chessmen[3].hashCode()));
      
              //展示每个棋子的颜色(棋子的内部状态)和坐标(棋子的外部状态)
              System.out.print("chessmen[0] ==> ");
              chessmen[0].showColor();
              chessmen[0].showCoordinates(new Coordinates(1,2));
              System.out.println();
              System.out.print("chessmen[1] ==> ");
              chessmen[1].showColor();
              chessmen[1].showCoordinates(new Coordinates(3,4));
              System.out.println();
              System.out.print("chessmen[2] ==> ");
              chessmen[2].showColor();
              chessmen[2].showCoordinates(new Coordinates(5,6));
              System.out.println();
              System.out.print("chessmen[3] ==> ");
              chessmen[3].showColor();
              chessmen[3].showCoordinates(new Coordinates(7,8));
              System.out.println();
      
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47

      测试结果:

      image-20221116202512335
      csdn

    🌲总结

    • 享元模式通过享元池能够进行对象的复用,很大程度的节约了系统的内存,但是享元对象必须是较小粒度,否则会占用较大内存,同时需要注意外部状态和内部状态的划分。关于Flyweight模式,一言以蔽之就是“通过尽量共享实例来避免new出实例

    • 相关设计模式

      • Singleton 模式:在FlyweightFactory角色中有时会使用Singleton模式。此外,如果使用了Singleton模式,由于只会生成一个Singleton角色,因此所有使用该实例的地方都共享同一个实例。在Singleton角色的实例中只持有intrinsic信息

      • Facatory 模式:享元模式一般都需要搭配工厂模式一起实现,才能最大程度减少内存的消耗(它们是天造地设的一对)

      • Composite 模式:有时可以使用Flyweight模式共享Composite模式中的Leaf角色

      • Proxy 模式:如果生成实例的处理需要花费较长时间,那么使用Flyweight模式可以提高程序的处理速度而Proxy模式则是通过设置代理提高程序的处理速度

    自此,文章就结束了,如果觉得本文对你有一丢丢帮助的话😄,欢迎点赞👍+评论✍,您的支持将是我写出更加优秀文章的动力O(∩_∩)O

    拜拜


    上一篇:每日一个设计模式之【外观模式】

    下一篇:每人一个设计模式之【代理模式】

    参考文章

    • 图解设计模式
    • 菜鸟教程

    在次致谢

  • 相关阅读:
    Vulnhub靶场 ICA: 1
    [dp]Matryoshka Doll 2022杭电多校第9场 1007
    (免费领源码)java#SSM#Mysql学院教室管理系统81671-计算机毕业设计项目选题推荐
    SLAM中的李群和李代数
    Golang——从入门到放弃
    在 GeoServer 上发布 Shapefile 文件作为 WMS 数据
    pandas练习
    云原生|kubernetes|找回丢失的etcd集群节点---etcd节点重新添加,扩容和重新初始化k8s的master节点
    5G专网融合时间敏感网络架构技术
    观测云产品更新|新增基础设施 YAML 显示;新增日志查看器 DQL 搜索模式;优化应用性能监测等
  • 原文地址:https://blog.csdn.net/qq_66345100/article/details/127893730