软件设计模式学习(十五)享元模式
当系统中存在大量相同或相似的对象时,享元模式是一种较好的解决方案,它通过共享技术实现相同或相似的细粒度对象的复用,从而节约内存空间。享元模式提供了一个享元池用于存储已经创建好的享元对象,并通过享元工厂类将享元对象提供给客户端使用。
模式动机
使用面向对象技术开发时,很多情况下需要在系统中增加类和对象的个数,并且这些对象有些是相同或相似的。当对象太多时,将导致运行代价过高,性能下降等问题。为了避免系统中出现大量相同或相似的对象,享元模式通过共享技术实现相同或相似对象的重用,相同的对象都指向一个实例,存储这个实例的对象称为享元池。
模式设计
系统中有些对象并不完全相同,而只是相似,因此需要先找出这些对象的共同点,在享元类中封装这些共同的内容。不同的内容可以通过外部应用程序来设置,而不进行共享,在享元模式中可以共享的相同内容称为内部状态,而那些需要外部环境设置的不能共享的内容称为外部状态。
在享元模式中通常会出现工厂模式,需要创建一个享元工厂来维护一个享元池,用于存储具有相同内部状态的享元对象。实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种状态一般称为细粒度对象。享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。
模式定义
运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此又称为轻量级模式。
模式结构
-
Flyweight(抽象享元类)
抽象享元类声明是一个接口,通过它可以接受并作用于外部状态。在抽象享元类定义了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)
-
ConcreteFlyweight(具体享元类)
具体享元类实现了抽象享元接口,保存了内部状态,具体享元对象是可以共享的。可以结合单例模式来设计享元具体类,为每一个具体享元类提供唯一的享元对象。
-
UnsharedConcreteFlyweight(非共享具体享元类)
不能被共享的抽象享元类的子类被设计为非共享具体享元类。当需要一个非共享具体享元类的对象时可以直接通过实例化创建。在某些享元模式的层次结构中,非共享具体享元对象还可以将具体享元对象作为子节点。
-
FlyweightFactory(享元工厂类)
享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中。当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池已创建的实例或者创建一个实例,返回该新创建的实例并将其存储在享元池中。
模式分析
享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池。典型的享元工厂类代码如下:
public class FlyweightFactory {
private HashMap flyweights = new HashMap();
public Flyweight getFlyweight(String key) {
if (flyweights.containsKey(key)) {
return (Flyweight) flyweights.get(key);
} else {
Flyweight fw = new ConcreteFlyweight();
flyweights.put(key, fw);
return fw;
}
}
}
享元对象能做到共享的关键是区分内部状态(internal state)和外部状态(external state)。下面简单对享元的内部状态和外部状态进行分析:
- 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享。
- 外部状态是随环境改变而改变的、不可共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用时再传入到享元对象内部。
典型的享元类代码如下:
public class Flyweight {
private String intrinsicState;
public Flyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
public void operation(String extrinsicState) {
...
}
}
实例之共享网络设备
很多网络设备都是支持共享的,如交换机、集线器等,多台计算机终端可以链接同一台网络设备,并通过该网络设备进行数据转换。但是分配给每一个终端计算机的端口是不同的,可以将端口从网络设备中抽取出来作为外部状态,需要时再设置。
-
抽象享元类 NetworkDevices
public interface NetworkDevice { public String getType(); public void use(Port port); // 用于设置外部状态 }
-
具体享元类 Switch
public class Switch implements NetworkDevice { private String type; public Switch(String type) { this.type = type; } public String getType() { return type; } public void use(Port port) { System.out.println("Linked by switch, type is" + this.type + ",port is" + port.getPort()); } }
-
具体享元类 Hub
public class Hub implements NetworkDevice { private String type; public Hub(String type) { this.type = type; } public String getType() { return type; } public void use(Port port) { System.out.println("Linked by Hub, type is" + this.type + ",port is" + port.getPort()); } }
-
端口类 Port
public class Port { String port; public Port(String port) { this.port = port; } public String getPort() { return port; } public void setPort(String port) { this.port = port; } }
-
享元工厂 DeviceFactory
public class DeviceFactory { private List<NetworkDevice> devices = new ArrayList<NetworkDevice>(); private int totalTerminal = 0; public DeviceFactory() { NetworkDevice nd1 = new Switch("Cisco-WS-C2950-24"); devices.add(nd1); NetworkDevice nd2 = new Hub("TP-LINK-HF8M"); devices.add(nd2); } public NetworkDevice getNetworkDevice(String type) { if (type.equalsIgnoreCase("cisco")) { totalTerminal++; return devices.get(0); } else if (type.equalsIgnoreCase("tp")) { totalTerminal++; return devices.get(1); } else { return null; } } public int getTotalDevice() { return devices.size(); } public int getTotalTerminal() { return totalTerminal; } }
-
客户测试类 Client
public class Client { public static void main(String[] args) { DeviceFactory df = new DeviceFactory(); NetworkDevice nd1 = df.getNetworkDevice("cisco"); nd1.use(new Port("1000")); NetworkDevice nd2 = df.getNetworkDevice("cisco"); nd2.use(new Port("1001")); NetworkDevice nd3 = df.getNetworkDevice("cisco"); nd3.use(new Port("1002")); NetworkDevice nd4 = df.getNetworkDevice("tp"); nd4.use(new Port("1003")); NetworkDevice nd5 = df.getNetworkDevice("tp"); nd5.use(new Port("1004")); System.out.println("Total Device: " + df.getTotalDevice()); System.out.println("Total Terminal: " + df.getTotalTerminal()); } }
在客户端代码中,在调用享元对象的 use() 方法时,传入了一个 Port 类型对象,在该对象中封装了端口号,作为共享网络设备的外部状态,同一个网络设备具有多个不同的端口号。
从运行结果可以得知,在调用享元对象的 use() 方法时,由于设置了不同的端口号,因此相同的享元对象虽然具有相同的内部状态 type,但是它们的外部状态 port 不同。
模式优缺点
优点如下:
- 极大减少内存中对象的数量
- 享元模式的外部状态相对独立,不会影响其内部状态,因此享元对象可以在不同的环境中被共享。
缺点如下:
- 享元模式是系统更加复杂,需要分离出内部状态和外部状态。
- 读取外部状态会使运行时间变长。
模式适用环境
以下情况可以使用享元模式:
- 一个系统有大量相同或相似对象,这类对象的大量使用造成内存的大量耗费
- 对象的大部分状态都可外部化,可以将这些外部状态传入对象中。
- 维护享元池需要耗费资源,因此应当在多次重复使用享元对象时才值得使用享元模式
单纯享元模式
即所有抽象享元类的子类都可以共享,不存在非共享具体享元类
复合享元模式
将单纯享元模式与组合模式加以组合,可以形成复合享元对象。这样的复合享元对象本身不能共享,但它们可以分解成单纯享元对象,而后者可以共享。通过复合享元模式,可以确保复合享元类 CompositeConcreteFlyweight 中所包含的每个单纯享元类 ConcreteFlyweight 都具有相同的外部状态,而这些单纯享元的内部状态往往不同。
原文地址:https://www.cnblogs.com/Yee-Q/p/12859267.html
- 中小企业如何选择DDoS防御方案?
- “熊医生”出诊正确率超九成 医院:人工智能更多是辅助
- PLC编程优化方法,让程序运行提速!
- 这是硅谷狂人马斯克对未来做出的11个大胆预测,人工智能比核武器更危险
- 在腾讯云上使用自建DNS
- Spring 4.0.2 学习笔记(1) - 最基本的注入
- 关于女神SQLite的疑惑(2)
- WordPress纯代码仿无觅相关文章图文模式功能(增强版)
- 人工智能时代已悄然来临……
- 人民日报发布周鸿祎署名文章:迎接“大安全”时代的新威胁
- Mono 3.2 上跑NUnit测试
- 为WordPress 文章中的链接自动添加 nofollow标签
- 腾讯刘炽平:海外用户破7000万 微信带开发者“出海”
- 研究称性别不均衡或导致人工智能持有性别偏见
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 安全服务之安全基线及加固(三)Apache篇
- 使用docsify来管理文献
- Cypress系列(41)- Cypress 的测试报告
- SSRF绕过
- 性能测试必备知识(6)- 如何查看“CPU 上下文切换”
- flex布局 div盒子居中
- 使用Apple Configurator 2提取商店ipa or app文件
- Spring 自动装配模式之byType
- 使用ATOMac进行Mac自动化测试
- 【赵渝强老师】什么是Oracle的数据字典?
- antd 如何在 src目录下 引入 Public 目录下的文件
- (精编)Python与安全(三)SSTI服务器模板注入
- 一年经验Java开发0713面试
- 【分享】MicroBlaze大内部存储器(AXI BRAM)设计
- Spring Beans 自动装配