每日一个设计模式之【享元模式】
☁️前言🎉🎉🎉
大家好✋,我是知识汲取者😄,今天给大家带来一篇有关享元模式的学习笔记。众所周知能够熟练使用设计模式是一个优秀程序猿的必备技能,当我们在项目中选择一个或多个合适的设计模式,不仅能大大提高项目的稳健性、可移植性、可维护性,同时还能让你的代码更加精炼,具备艺术美感。
有时候我们在系统中由于对象创建的数量过多,会造成内存溢出,比如我们设计一个下围棋的程序,黑子有181颗、白子有180颗,总共有361颗,如果我们一股脑地不加以注意,每落一颗子就创建一个对象的化,那问题就大量,得创建大量对象。而享元模式的出现就能很好地解决这一弊端,很大程度地节约了内存,同时也能够提高程序的性能,享元模式有类似于线程池,它可以提供一个享元池,享元池中的对象可以被共享,能够被系统复用,从而大大降低对象的创建数量……话不多说,且看下文
推荐阅读:
🌻享元模式概述
-
什么是享元模式?
享元模式(Flyweight Pattern)是一种结构型模式,它利用共享技术实现对象的复用,进而减少内存占用和提高程序性能
-
享元模式的作用:减少对象的创建,减少内存占用和提高程序性能
-
享元模式的优缺点
-
优点:
- 提高系统性能。共享模式能够使用一个”享元池“存储提前需要创建的对象,后续需要使用对象可以直接从享元池中获取,而无需去创建,提高对象的创建速度
- 节约内存。共享模式让对象能够共享使用,进而复用,大大节约了内存的消耗
……
-
缺点:
- 具有一定局限性。享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式
- 提高了系统的复杂度。享元模式需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱,并且还可能会引起线程安全问题
- 过渡使用会造成不必要的内存资源浪费。享元模式中享元池的对象都是提前创建的,会占用系统的资源
……
-
-
享元模式的引用场景:
-
系统需要创建大量粒度较小且相似的对象,可以使用享元模式
-
系统需要一个缓冲池,可以使用享元模式进行实现
……
-
-
享元模式的角色划分:
- 抽象享元类(Flyweight):是一个具体实体的抽象类,内部含有所有具体享元类公有的方法,可以是抽象类、接口
- 具体享元类(ConcreteFlyweight):是一个具体类,内部存储了内部状态和外部状态(也可以将这两个状态存储在抽象享元类中),其实例化对象是享元对象
- 非共享享元类(UnsharedConcreteFlyweight):是一个具体的类,是享元对象的外部状态
- 享元工厂(FlyweightFactory):享元模式中的核心角色,内部含有一个享元池,用于提前创建并存储需要使用的享元对象
- 客户端(Client):系统的外部交互者(对应本文示例中的测试类)
-
相关概念:
- 内部状态(Intrinsic State):存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享(例如:围棋的颜色)
- 外部状态(Extrinsic State):随环境改变而改变的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的,且不可共享(例如:围棋的坐标)
🌱享元模式的实现
示例:
围棋分为黑子和白子两种,所有的棋子非黑即白,所以棋子的颜色是可以被共享的,这是围棋的内部状态;所有的围棋在落子后都有一个独一无二的坐标,所以围棋的落子坐标是不可以被共享的,这是围棋的外部状态。使用享元模式实现围棋对象的创建
-
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(); } }
测试结果:
🌲总结
-
享元模式通过享元池能够进行对象的复用,很大程度的节约了系统的内存,但是享元对象必须是较小粒度,否则会占用较大内存,同时需要注意外部状态和内部状态的划分。关于Flyweight模式,一言以蔽之就是“通过尽量共享实例来避免new出实例”
-
相关设计模式
-
Singleton 模式:在FlyweightFactory角色中有时会使用Singleton模式。此外,如果使用了Singleton模式,由于只会生成一个Singleton角色,因此所有使用该实例的地方都共享同一个实例。在Singleton角色的实例中只持有intrinsic信息
-
Facatory 模式:享元模式一般都需要搭配工厂模式一起实现,才能最大程度减少内存的消耗(它们是天造地设的一对)
-
Composite 模式:有时可以使用Flyweight模式共享Composite模式中的Leaf角色
-
Proxy 模式:如果生成实例的处理需要花费较长时间,那么使用Flyweight模式可以提高程序的处理速度而Proxy模式则是通过设置代理提高程序的处理速度
-
自此,文章就结束了,如果觉得本文对你有一丢丢帮助的话😄,欢迎点赞👍+评论✍,您的支持将是我写出更加优秀文章的动力O(∩_∩)O
上一篇:每日一个设计模式之【外观模式】
下一篇:每人一个设计模式之【代理模式】
参考文章:
- 图解设计模式
- 菜鸟教程
在次致谢