【设计模式】结构型模式—享元模式(Flyweight Pattern)(十二)
2021/7/25 23:42:39
本文主要是介绍【设计模式】结构型模式—享元模式(Flyweight Pattern)(十二),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
文章目录
- 前言
- 一.享元模式
- 二.享元模式适用场景
- 三.享元模式角色
- 四.享元模式的实现方式
- 1.单纯享元模式
- Flyweight(抽象享元角色)
- ConcreteFlyweight(具体享元角色)
- UnsharedConcreteFlyweigh( 非共享的享元角色)
- FlyweightFactory(享元工厂角色)
- 调用端
- 2.复合享元模式
- Flyweight(抽象享元角色)
- ConcreteFlyweight(具体享元角色)
- UnsharedConcreteFlyweigh( 复合享元角色)
- FlyweightFactory(享元工厂角色)
- 调用端
- 五.总结
- 1.注意事项
- 2.享元模式的优缺点
- 3.享元模式在Java中的应用
前言
建议先看这篇文章【设计模式】软件设计7大原则以及23种设计模式初识(一)
一.享元模式
享元模式(Flyweight Pattern)是 "资源池技术"实现方式,主要用于减少创建对象的数量
,以减少内存占用和提高性能。
-
模式动机:在创建大量对象时,有可能会造成内存溢出,享元模式把其中共同的部分抽象出来,保存在内存中,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
-
解决思路:复用对象最简单的方式是,用一个 HashMap 来存放每次新生成的对象。每次需要一个对象的时候,先到 HashMap 中看看有没有,如果没有,再生成新的对象,然后将这个对象放入 HashMap 中。
-
享元模式通常需要和其他模式一起联用,几种常见的联用方式如下:
- 享元模式的享元工厂类中通常
提供一个静态的工厂方法
用于返回享元对象,使用简单工厂模式
来生成享元对象 - 在一个系统中,
通常只有唯一一个享元工厂
,因此可以使用单例模式
进行享元工厂类的设计。 - 享元模式可以
结合组合模式形成复合享元模式
,统一对多个享元对象设置外部状态。
- 享元模式的享元工厂类中通常
二.享元模式适用场景
有大量相同或者相似的对象
,造成内存的大量耗费- 对象的
大部分状态都可以外部化
,可以将这些外部状态传入对象中。 - 需要缓冲池的场景
在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式
三.享元模式角色
-
Flyweight(抽象享元角色) :
享元对象的抽象基类或者接口
,声明所有具体享元角色需要实现的方法,这些方法可以向外界提供对象的内部状态,设置外部状态
。- 内部状态(intrinsic):是
享元对象可共享的属性
,存储在享元对象内部并且不会随环境改变而改变。 - 外部状态(extrinsic):是
对象得以依赖的一个标记
,是随环境改变而改变的、不可以共享的状态。
- 内部状态(intrinsic):是
-
ConcreteFlyweight( 具体享元角色):
Flyweight的具体实现
,实现抽象角色定义的方法。 为内部状态提供成员变量进行存储 -
UnsharedConcreteFlyweigh( 非共享的享元角色):
不能被共享的子类
可以设计为非共享享元类 -
FlyweightFactory(享元工厂角色):
负责管理享元对象池和创建享元对象
,就是构造一个池容器,同时提供从池中获得对象的方法
,- 当请求获取一个享元对象时,享元工厂判断是否存在该享元对象,如果存在则返回;如果不存在的话,则创建一个新的享元对象,保存到池容器中,然后返回给请求者。
享元池一般设计成键值对
。享元模式一般都是和工厂模式一起出现
享元模式状态
享元对象能做到共享的关键是区分了内部状态和外部状态。
案例:每个围棋棋子都是一个享元对象,
- 有如下属性:
四.享元模式的实现方式
享元模式可以分成单纯享元模式和复合享元模式两种形式
- 单纯享元模式:所有的具体享元类都是可以共享的,不存在非共享具体享元类
- 复合享元模式:将一些
单纯享元对象
使用组合模式加以组合
,还可以形成复合享元对象,这样的复合享元对象本身不能共享,但是可以分解成单纯享元对象,而后者则可以共享
。
通过复合享元模式,可以确保复合享元类CompositeConcreteFlyweight中所包含的每个单纯享元类ConcreteFlyweight都具有相同的外部状态,而这些单纯享元的内部状态往往可以不同。如果希望为多个内部状态不同的享元对象设置相同的外部状态,可以考虑使用复合享元模式
。
1.单纯享元模式
Flyweight(抽象享元角色)
/** * 抽象享元类 */ public interface ChessFlyWeight { //设置颜色 void setColor(String c); //获取颜色 String getColor(); //展示棋子信息 void show(Coordinate c); }
ConcreteFlyweight(具体享元角色)
/** *具体享元类-棋子 * */ class ConcreteChess implements ChessFlyWeight { private String color;//外部状态 public ConcreteChess(String color) { super(); this.color = color; } @Override public void show(Coordinate c) { System.out.println("棋子颜色:"+color); System.out.println("棋子位置:"+c.getX()+"----"+c.getY()); } @Override public String getColor() { return color; } @Override public void setColor(String c) { this.color = c; } }
UnsharedConcreteFlyweigh( 非共享的享元角色)
/** * 外部状态 */ public class Coordinate { private int x,y; public Coordinate(int x, int y) { super(); 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; } }
FlyweightFactory(享元工厂角色)
工厂类
用于对享元对象
进行管理和扩展,尝试重用现有的同类对象,如果未找到匹配的对象,再创建新对象。
/** *享元工厂类-创建棋子 */ public class ChessFlyWeightFactory { //享元池 根据颜色保存不同的棋子对象 private static Map<String,ChessFlyWeight> map = new HashMap<String, ChessFlyWeight>(); public static ChessFlyWeight getChess(String color){ // 如果已经存在该颜色的棋子返回 if(map.get(color)!=null){ return map.get(color); } // 如果享元池中没有该颜色的棋子就创建一个新的返回 并存储在享元池中 ChessFlyWeight cfw = new ConcreteChess(color); map.put(color, cfw); return cfw; } }
调用端
public class Client { public static void main(String[] args) { //获取黑色棋子 ChessFlyWeight chess1 = ChessFlyWeightFactory.getChess("黑色"); //再次获取黑色棋子 ChessFlyWeight chess2 = ChessFlyWeightFactory.getChess("黑色"); //获取白色色棋子 ChessFlyWeight chess3 = ChessFlyWeightFactory.getChess("白色"); System.out.println("获取黑色棋子:"+chess1); System.out.println("再次获取黑色棋子:"+chess2); System.out.println("获取白色色棋子"+chess3); System.out.println("===========增加外部状态的处理==========="); chess1.show(new Coordinate(10, 10)); chess2.show(new Coordinate(20, 20)); } }
测试结果
2.复合享元模式
Flyweight(抽象享元角色)
/** * 抽象享元类 */ public interface Flyweight{ void operation(String extrinsicState); }
ConcreteFlyweight(具体享元角色)
/** * 具体享元 */ public class ConcreteFlyweight implements Flyweight{ private Character intrinsicState; public ConcreteFlyweight(Character intrinsicState){ this.intrinsicState = intrinsicState; } /** * 外部状态改变方法行为,但不会改变内部状态 */ @Override public void operation(String extrinsicState){ String str = "intrinsic:" + intrinsicState; str += " extrinsic:" + extrinsicState; System.out.println(str); } }
UnsharedConcreteFlyweigh( 复合享元角色)
- 复合享元是不能共享的,所以叫
UnsharableFlyweight
。但是复合享元是可以分解为可共享的单纯享元
。
/** * 复合享元 */ class UnsharableFlyweight implements Flyweight { private Map<Character, Flyweight> map; public UnsharableFlyweight() { map = new HashMap<Character, Flyweight>(); } public void add(Character c, Flyweight fly) { map.put(c, fly); } @Override public void operation(String extrinsicState) { Iterator<Map.Entry<Character, Flyweight>> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry<Character, Flyweight> entry = it.next(); Flyweight fly = entry.getValue(); fly.operation(extrinsicState); } } }
FlyweightFactory(享元工厂角色)
工厂类
用于对享元对象
进行管理和扩展,尝试重用现有的同类对象,如果未找到匹配的对象,再创建新对象。
/** * 复合享元工厂 */ public class FlyweightFactory { private Map<Character, Flyweight> map; public FlyweightFactory() { map = new HashMap<Character, Flyweight>(); } /** * 单纯享元工厂 */ public Flyweight factory(Character state) { Flyweight flyweight = null; if (map.containsKey(state)) { flyweight = map.get(state); } else { flyweight = new ConcreteFlyweight(state); map.put(state, flyweight); } return flyweight; } /** * 复合享元工厂 * 此处Character的复合类型恰好是String, * 当无此巧合时,可使用List等聚集类型传入. */ public Flyweight factory(String compositeState) { UnsharableFlyweight composite = new UnsharableFlyweight(); for (int i = 0; i < compositeState.length(); i++) { Character c = compositeState.charAt(i); composite.add(c, this.factory(c)); } return composite; } /** * 辅助方法 */ public void checkFlyweight() { Iterator<Map.Entry<Character, Flyweight>> it = map.entrySet().iterator(); System.out.println("check flyweight:"); while (it.hasNext()) { Map.Entry<Character, Flyweight> entry = it.next(); Character key = entry.getKey(); System.out.println("key:" + key); } } }
调用端
public class Client{ public static void main(String[] args){ FlyweightFactory f = new FlyweightFactory(); Flyweight fly = f.factory("aba"); fly.operation("ex"); f.checkFlyweight(); } }
测试结果
五.总结
1.注意事项
- 多线程场景又会带来
线程安全
的问题,该如何设计需要依靠经验权衡。 外部状态(通常就是 HashMap 的键)尽量使用基本数据类型
。如果使用对象类型作为外部状态
,必须重写 equals 方法和 hashCode 方法
。
2.享元模式的优缺点
优点:
- 极大
减少内存中对象的数量
,相同或相似对象内存中只存一份,降低程序内存的占用,增强程序的性能。 - 外部状态相对独立,不影响内部状态
缺点:
- 提高了系统复杂性,需要分离出外部状态和内部状态,而且外部状态不应该随内部状态改变而改变,否则导致系统的逻辑混乱。
- 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态使运行时间变长。用时间换取了空间。
3.享元模式在Java中的应用
-
字符串常量池:字符串当第一次使用之后,就会存在于常量池内,下次使用时可以直接从常量池内取出,不需要重复创建。
-
所有基本类型的包装类:如: Integer中也使用到了享元模式。
来看看valueOf的源码:
这里用到了享元模式,首先会去缓存里面取,但是我们看到取缓存时候有个条件,那就是数字必须在low和high之间
:
可以看到low是-128,high默认是127
,Interger在第一次加载时会初始化-128-127之前对象
- 为什么限定-128~127之间才缓存因为
这个区间的数字是最常用的
。其他的基本类型的包装类型也使用了享元模式,在这里就不继续举例了。
- 数据库连接池。
- 线程池
这篇关于【设计模式】结构型模式—享元模式(Flyweight Pattern)(十二)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-19JAVA分布式id教程:轻松入门与实践
- 2024-11-19Java高并发教程:入门与实践指南
- 2024-11-19JAVA高并发直播教程:新手入门指南
- 2024-11-19Java高并发直播教程:入门与实践指南
- 2024-11-19Java微服务教程:初学者快速入门指南
- 2024-11-19JAVA微服务教程:新手入门的详细指南
- 2024-11-19Java微服务教程:从零开始搭建你的第一个微服务应用
- 2024-11-19Java项目开发教程:初学者必备指南
- 2024-11-19Java项目开发教程:新手快速入门指南
- 2024-11-19Java项目开发教程:零基础入门到实战