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

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

☁️前言🎉🎉🎉

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

  有时候我们在系统中由于对象创建的数量过多,会造成内存溢出,比如我们设计一个下围棋的程序,黑子有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() + ")");
        }
    
    }
    
  • 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;
        }
    
    }
    

    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;
        }
    
    }
    
  • 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;
        }
    }
    
  • 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);
        }
    }
    
  • 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();
    
        }
    }
    

    测试结果:

    image-20221116202512335
    csdn

🌲总结

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

  • 相关设计模式

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

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

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

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

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

拜拜


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

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

参考文章

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

在次致谢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知识汲取者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值