十二、享元模式
文章目录
- 1 基本介绍
- 2 案例
- 2.1 Digit 接口
- 2.2 Color 枚举
- 2.3 BigDigit 类
- 2.4 DigitFactory 类
- 2.5 Client 类
- 2.6 Client 类的测试结果
- 2.7 总结
- 3 各角色之间的关系
- 3.1 角色
- 3.1.1 Flyweight ( 抽象享元 )
- 3.1.2 ConcreteFlyweight ( 具体享元 )
- 3.1.3 UnsharedFlyweight ( 非享元 )
- 3.1.4 FlyweightFactory ( 享元工厂 )
- 3.1.5 Client ( 客户端 )
- 3.2 类图
- 4 注意事项
- 5 在源码中的使用
- 6 优缺点
- 7 适用场景
- 8 总结
1 基本介绍
享元模式(Flyweight Pattern)是一种 结构型 设计模式,其核心思想是通过 共享某些对象 来 复用 它们,和单例模式的单例有相似之处——共享实例对象。
此处的翻译并非直译,中文和英文对应的含义如下:
- 对于中文 享元模式,“享元”实际上是“共享实例对象”的意思。
- 对于英文 Flyweight,Flyweight 的中文翻译是 蝇量级,这个概念原本是拳击比赛的一种重量级,用在此处表示这种模式可以使程序变得更“轻”,即 占用的内存更少。
2 案例
本案例实现了 打印不同颜色的大数字(虽然只有 3 种颜色、3 种大数字)。
2.1 Digit 接口
public interface Digit { // 数字接口void print(Color color); // 按照指定颜色打印对象中所存储的数
}
2.2 Color 枚举
public enum Color { // 颜色的枚举GREEN("\u001B[32m"), // 绿色BLUE("\u001B[34m"), // 蓝色PURPLE("\u001B[35m"); // 紫色Color(String colorStr) {this.colorStr = colorStr;}private String colorStr; // 保存颜色的字符串public String getColorStr() {return colorStr;}
}
2.3 BigDigit 类
import java.io.PrintStream;public class BigDigit implements Digit { // 大数字private String pattern; // 大数字的图像public BigDigit(int num) {switch (num) {case 1:pattern = DIGIT_1_PATTERN;break;case 2:pattern = DIGIT_2_PATTERN;break;case 3:pattern = DIGIT_3_PATTERN;break;default:pattern = "尚未实现,敬请期待!";}}// 按照指定的颜色打印大数字的图像@Overridepublic void print(Color color) {PrintStream defaultPrintStream = System.out; // 保存默认的打印流System.setOut(new ColoredDigitPrintStream(color)); // 将打印流更换为指定颜色的打印流System.out.println(pattern);System.setOut(defaultPrintStream); // 还原默认的打印流}private static final String DIGIT_1_PATTERN = """......................##..........######..............##..............##..............##..............##..........##########...................."""; // 大数字 1 的图像private static final String DIGIT_2_PATTERN = """....................######........##......##..............##..........####..........##............##..............##########...................."""; // 大数字 2 的图像private static final String DIGIT_3_PATTERN = """....................######........##......##..............##..........####................##......##......##........######......................"""; // 大数字 3 的图像private static class ColoredDigitPrintStream extends PrintStream { // 带有颜色的数字打印流private Color color; // 打印流的颜色public ColoredDigitPrintStream(Color color) {super(System.out);this.color = color;}@Overridepublic void println(String x) {super.println(this.color.getColorStr() + x + DEFAULT_COLOR);}private static final String DEFAULT_COLOR = "\u001B[0m"; // 默认的字符颜色}
}
2.4 DigitFactory 类
public class DigitFactory { // 数字的工厂// 数字池,存储 int 数 与 数字 的映射private static Map<Integer, Digit> digitPool = new HashMap<>();/*** 获取指定 int 数对应的数字* 注意本方法是 synchronized 的,防止多个线程同时创建多个共享实例,类似于单例模式** @param num 指定的 int 数* @return 返回指定 int 数对应的数字*/public synchronized static Digit getInstance(int num) {if (!digitPool.containsKey(num)) { // 如果 digitPool 中不存在 num 对应的数字digitPool.put(num, new BigDigit(num)); // 则创建新的大数字}return digitPool.get(num); // 返回 digitPool 中 num 对应的数字}
}
2.5 Client 类
public class Client { // 客户端,测试大数字的输出public static void main(String[] args) {// 可以通过 digit1 打印不同颜色的 1Digit digit1 = DigitFactory.getInstance(1);digit1.print(Color.BLUE);digit1.print(Color.PURPLE);Digit digit2 = DigitFactory.getInstance(2);digit2.print(Color.GREEN);Digit digit3 = DigitFactory.getInstance(3);digit3.print(Color.PURPLE);Digit digit3_1 = DigitFactory.getInstance(3);System.out.println(digit3 == digit3_1); // true,因为 digit3 和 digit3_1 是同一个对象}
}
2.6 Client 类的测试结果
注意:文章中无法显示大数字的颜色,可以自己在 IDEA 上运行一下,即可看到不同的颜色。
................
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
................
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
................
....######......
..##......##....
..........##....
......####......
..........##....
..##......##....
....######......
................
true
2.7 总结
通过使用享元模式,减少了创建大数字对象的机会,从而降低了程序的内存占用;通过 Map
管理生成的实例,避免了多次 new
新对象耗费的时间。可谓是时间和空间上的双赢。
此外,认真观察大数字的两个“属性”:图像 和 颜色,可以发现:
- 图像是固定的,不论任何情况都不会改变,所以图像是 应当共享的信息,将其放到
BigDigit
中。 - 颜色不是固定的,随着
Client
需要的颜色而变化,所以颜色是 不应当共享的信息,将其放到其他类Color
中。
3 各角色之间的关系
3.1 角色
3.1.1 Flyweight ( 抽象享元 )
该角色负责 将 UnsharedFlyweight 角色作为参数,定义 ConcreteFlyweight 角色需要实现的方法。如果系统的功能很简单,则不需要该角色。本案例中,Digit
接口扮演该角色。
3.1.2 ConcreteFlyweight ( 具体享元 )
该角色负责 实现 Flyweight 角色定义的方法,定义可以共享的部分。本案例中,BigDigit
类扮演该角色。
3.1.3 UnsharedFlyweight ( 非享元 )
该角色负责 定义无法共享的部分,作为参数传入 Flyweight 角色定义的方法中。本案例中,Color
枚举扮演该角色。
3.1.4 FlyweightFactory ( 享元工厂 )
该角色负责 生成可共享的 Flyweight 角色,内部有一个 池 的字段来保存已生成的 Flyweight 实例,当传入相同的参数时,返回这些已有的实例。要注意 getInstance()
方法需要被 synchronized
关键字修饰,以保证线程安全。本案例中,DigitFactory
类扮演该角色。
3.1.5 Client ( 客户端 )
该角色负责 使用 FlyweightFactory 角色来生成 Flyweight 角色。本案例中,Client
类扮演该角色。
3.2 类图
说明:FlyweightFactory 角色的 pool
字段 和 getInstance()
方法都是 静态 的,使用了 简单(静态)工厂模式。
4 注意事项
- 划分外部状态和内部状态:正确划分 内部状态 和 外部状态 是享元模式应用的关键。
- 内部状态:指对象中 可以共享的信息,这部分信息 存储在享元对象内部,不会随环境的改变而改变。
- 外部状态:指对象中 不可共享的信息,这部分信息 在享元对象外部用新的对象来存储,随环境改变而改变,在方法调用时 以参数形式传入。
- 生成共享实例的工厂:享元模式中的类通常需要一个 工厂 来 控制对象的 创建 和 管理。当客户对象请求一个享元对象时,享元工厂会 先检查系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在,则创建一个新的享元对象。
- 线程安全问题:由于享元模式中的对象可能被多个线程下的客户端 共享,因此必须确保这些对象在并发环境下是线程安全的。这可能需要使用 同步机制 来 保护共享对象的内部状态。
- 性能考虑:虽然享元模式可以显著减少对象的创建和内存使用,但 读取外部状态 可能 会使运行时间稍微变长。在性能敏感的应用中,需要仔细评估这一点。
5 在源码中的使用
在 JDK 中的 Integer
类就使用了享元模式的思想,(如果不设置 VM 参数,则)为常用的 int
数(范围为 [-128, 127]
)提前初始化其对应的 Integer
对象。
在调用 Integer.valueOf()
时,如果传入的参数在 [-128, 127]
之内,则返回提前初始化的 Integer
对象;否则就为传入的参数初始化一个新的 Integer
对象。
Integer
类的部分代码如下所示:
// java.lang.Integer 类
public final class Integer extends ... implements ... {public static Integer valueOf(int i) { // 相当于 享元工厂 的 getInstance()// 如果 i 在范围 [low, high] 内,则直接返回提前初始化好的 Integer 对象if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}private static class IntegerCache { // Integer 的缓存static final int low = -128; // 最小的 int 数static final int high; // 最大的 int 数,可能由 VM 参数指定static final Integer[] cache; // 保持不变的常量池static Integer[] archivedCache; // archive 中的缓存static {// high 的值可能被 VM 参数所指定int h = 127;String integerCacheHighPropValue =VM.getSavedProperty("java.lang.Integer.IntegerCache.high");if (integerCacheHighPropValue != null) {try {h = Math.max(parseInt(integerCacheHighPropValue), 127);// 缓存数组的最大长度是 Integer.MAX_VALUEh = Math.min(h, Integer.MAX_VALUE - (-low) -1);} catch( NumberFormatException nfe) {// 如果这个属性不能被解析成一个 int 数,则忽略这个异常}}high = h;// 如果可能的话,从 archive 中加载 IntegerCache.archivedCacheCDS.initializeFromArchive(IntegerCache.class); // 此方法是 native 方法int size = (high - low) + 1;// 如果 archive 缓存 存在 且 足够大,则使用它;否则构建新的缓存if (archivedCache == null || size > archivedCache.length) {// 将范围 [low, high] 内的 Integer 对象提前放入常量池Integer[] c = new Integer[size];int j = low;for(int i = 0; i < c.length; i++) {c[i] = new Integer(j++);}archivedCache = c;}cache = archivedCache;// 确保范围 [-128, 127] 内的 Integer 对象提前将其放入常量池assert IntegerCache.high >= 127;}private IntegerCache() {}}
}
说明:
Integer
类中 写一个静态内部类来创建共享的对象实例,这样可以避免给valueOf()
方法添加synchronized
修饰,因为 类的加载是线程安全 的,所以不可能生成两个相同值的Integer
共享实例。这种思想可以类比到 单例模式 的 静态内部类实现 中。- 在
Integer
类中:valueOf()
方法相当于 FlyweightFactory 角色的getInstance()
方法。IntegerCache.cache
相当于 FlyweightFactory 角色的pool
字段,用于存储已创建的共享实例。
Integer
类并没有完全使用享元模式,它只是提前初始化了部分高频使用的Integer
对象,将其共享,从而减少valueOf()
方法创建过多的对象,引发 内存溢出 问题。此外,这么做还可以减少构建共享对象所花费的时间,使Integer
类更加高效。
6 优缺点
优点:
- 减少内存消耗:享元模式通过 共享对象的内部状态 来 减少内存占用,从而避免为每个相似的对象都创建新的实例。
- 提高性能:由于 减少了对象的创建和销毁次数,享元模式可以 提高系统的性能。特别是在 频繁创建和销毁对象 的场景中,享元模式能够显著减少这些操作带来的开销。
- 降低系统复杂性:享元模式将对象的状态分为 内部状态 和 外部状态,使得系统更容易理解和维护。内部状态 由 享元对象 管理,外部状态 由 客户端 管理,这种分离 降低了系统的复杂性,并有效地 降低了对象间的耦合度。
缺点:
- 增加编程复杂性:实现享元模式需要将对象的状态分为 内部状态 和 外部状态,这要求开发者对系统的 状态管理 有深入的理解。同时,区分内部状态和外部状态可能会使代码逻辑变得复杂,增加编程的难度。
- 可能引入线程安全问题:如果多个线程同时访问和修改共享对象,可能会导致线程安全问题。因此,在使用享元模式时,需要特别注意线程安全的处理。
- 可能增加运行时开销:由于享元对象的 外部状态 是通过参数传递给享元对象的,这可能会增加运行时的开销。特别是在外部状态较多或频繁变化的情况下,这种开销可能会更加明显。
7 适用场景
- 相似对象的大量使用:当系统中存在大量相似对象,即这些对象只有少部分数据不同时,使用享元模式可以显著减少对象的数量,从而降低内存消耗。
- 对象具有许多共同属性:当对象具有许多 共同属性,而这些属性可以 被所有实例共享 时,使用享元模式可以避免在每个实例中重复存储这些属性,从而节省内存。
- 频繁创建和销毁对象:如果系统中频繁地创建和销毁大量对象,这会导致大量的内存分配和回收操作,从而降低系统的性能。使用享元模式可以减少对象的创建和销毁次数,从而提高性能。
- 需要快速响应时间的系统:在一些需要 快速响应时间 的系统中,如实时游戏、实时交易系统等,减少对象的创建和销毁时间 是非常重要的。享元模式可以通过减少对象的数量来降低这些操作的开销,从而提高系统的响应时间。
8 总结
享元模式 是一种 结构型 设计模式,其核心思想是通过 共享某些对象 来 复用 它们,和单例模式的单例有相似之处,可以减少 内存 和 时间 的消耗。
然而,在使用这种模式时不能随心所欲,共有三点需要特别考虑:
- 先明确对象的 外部状态 和 内部状态,然后将 内部状态 放到享元类中,使用享元工厂生产共享实例;将 外部状态 放到另外的类中,在调用共享实例的方法时将其传入。
- 由于享元模式可能应用到多线程的环境中,所以在工厂生成共享实例时需要 保证线程安全。
- 对于一个共享实例的多个引用,如果使用其中一个引用修改这个共享实例,那么会导致所有引用指向的实例都被修改,这也是共享的坏处之一,应该尽可能保证共享实例不会被修改。
相关文章:

十二、享元模式
文章目录 1 基本介绍2 案例2.1 Digit 接口2.2 Color 枚举2.3 BigDigit 类2.4 DigitFactory 类2.5 Client 类2.6 Client 类的测试结果2.7 总结 3 各角色之间的关系3.1 角色3.1.1 Flyweight ( 抽象享元 )3.1.2 ConcreteFlyweight ( 具体享元 )3.1.3 UnsharedFlyweight ( 非享元 )…...

黑马Java零基础视频教程精华部分_18_Arrays各种方法
系列文章目录 文章目录 系列文章目录Arrays简介Arrays各种方法toString代码示例binarySearch代码示例copyOf代码示例copyOfRange和fill代码示例sort代码示例 Arrays简介 操作数组的工具类。 Arrays各种方法 toString代码示例 int[]arr{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; //to…...

RAG私域问答场景超级详细方案(第一期方案)[1]:工业级别构建私域问答(知识处理、知识召回排序、搜索问答模块)
RAG私域问答场景整体夏详细方案(第一期方案):工业级别构建私域问答(知识处理、知识召回排序、搜索问答模块) 大模型性能的跳阶式增长给文本摘要、信息检索、信息抽取、语义问答等自然语言处理任务带来了卓越的性能提升。同时,LangChain 作为一种基于 LLM 的框架,能够快速…...

【AI在医疗领域的应用】AI在疾病诊断、个性化治疗等领域的应用
AI在医疗领域的应用 AI在疾病诊断、个性化治疗等领域的应用 引言 人工智能(AI)技术正在迅速改变各个行业,而医疗领域无疑是AI应用最广泛、影响最深远的领域之一。AI在医疗中的应用不仅能够提高诊断的准确性和效率,还为个性化治疗…...

SpEL结合AOP示例
AOP不用多说,是spring框架的两大基石之一。SpEL是Spring Expression Language的缩写,意为Spring表达式语言,,其支持在运行时查询和操作对象图提供了更加丰富的功能,最特别的是方法调用与字符串模板功能。熟悉js的es6语…...

【Linux:环境变量】
目录 命令行参数: 环境变量: 命令行参数: argv是一个char*类型的数组,里面存放着字符、字符串的指针地址,且该数组必定是以NULL结尾 命令行中启动的进程都是Bash的子进程,命令行参数的存在本质上就是通过…...

8月9日笔记
8月9日笔记 什么是代理? “代理”通常指的是“网络代理”,它是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。代理服务器作为中间人…...

API 签名认证:AK(Access Key 访问密钥)和 SK(Secret Key 私密密钥)
API签名认证 在当今的互联网时代,API作为服务与服务、应用程序与应用程序之间通信的重要手段,其安全性不容忽视。你是否遇到过需要在HTTP请求中加入访问密钥(ak)和私密密钥(sk)的情况?是不是担心这些敏感信息会被拦截或者泄露?本…...

Redis 单机和集群环境部署教程
目录 一、Redis 单机环境部署1. 环境准备2. 安装 Redis2.1 安装依赖2.2 下载并编译 Redis2.3 配置 Redis2.4 设置 Redis 为系统服务 3. Redis 配置选项详解4. 注意事项 二、Redis 集群环境部署1. 环境准备2. 安装 Redis3. 配置 Redis 集群3.1 配置文件调整3.2 启动 Redis 实例3…...

华为hcip-big data 学习笔记《一》大数据应用开发总指导
一、大数据应用开发总指导 1. 前言 随着大数据技术的飞速发展和大数据应用的不断普及,大数据已经成为当今时代最热门的话题之一。不过对于大数据的了解,很多人还只是停留在表面,提到大数据,很多人只是直到它是最新的科技&#x…...

用户画像架构图
背景 本文讲述下实现一个画像平台的架构图 架构图 这里面的人群圈选我们这里主要采用ck和spark,不过也有很多使用es,如果使用es的话,需要把标签的数据也存储到es的表中,类似我们这里放到ck的表中一样,这样就可以通过…...

37.x86游戏实战-XXX遍历怪物数组
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 工具下载: 链接:https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…...

go语言中map为什么不会自动初始化?
go语言中map为什么不会自动初始化? 在Go语言中,map类型不会自动初始化的原因在于其设计哲学和类型系统。以下是具体原因: 零值设计:Go语言中的每种类型都有一个零值,例如整型的零值是0,布尔型的零值是fals…...

大数据面试SQL(一):合并日期重叠的活动
文章目录 合并日期重叠的活动 一、题目 二、分析 三、SQL实战 四、样例数据参考 合并日期重叠的活动 一、题目 已知有表记录了每个品牌的活动开始日期和结束日期,每个品牌可以有多个活动。请编写一个SQL查询合并在同一个品牌举行的所有重叠的活动,…...

stm32应用、项目、调试
主要记录实际使用中的一些注意点。 1.LCD 1.LCD1602 电路图: 看手册:电源和背光可以使用5v或者3.3v,数据和控制引脚直接和单片机引脚连接即可。 单片机型号:stm32c031c6t6 可以直接使用推完输出连接D0--D7,RS,EN,RW引脚&#…...

WEB渗透-未授权访问篇
WEB渗透未授权访问篇-Redis-CSDN博客 activemq 默认端口8161,默认账户密码admin/admin http://1.1.1.1:8161/admin/connections.jsp PUT /fileserver/%2F%2F2%083.jsp HTTP/1.0 Content-Length: 27 Host: 1.1.1.1:8161 Connection: Close Authorization: Basic YW…...

x86_64、AArch64、ARM32、LoongArch64、RISC-V
以下是对 x86_64、AArch64、ARM32、LoongArch64 和 RISC-V 这几种计算机架构的介绍,包括它们的应用场景、优缺点: 1. x86_64 简介: x86_64 是由 AMD 推出的 64 位扩展版 x86 架构,兼容于英特尔的 IA-32 架构。这一架构被广泛应用于桌面和服…...

git push上不去的问题Iremote reiectedl——文件过大的问题
在新建分支的时候,发现push怎么也上传不上去,一开始觉得是权限的问题,但是尝试了各种方案都没有用,后面再仔细看了一下是文件太大了,远程拒绝推送 接下来,和大家讲讲我的解决方案 1、把修改的代码迁移到新…...

Qt Creator卡顿
删除IDE的配置参数的保存文件夹QtProject,使得Qt Creator恢复出厂值。 C:\Users\替换为你的用户名\AppData\Roaming\QtProject 参考链接: Qt Creator 卡顿 卡死...

数据结构笔记(其五)--串
目录 12.串 12.1 基本操作 12.2 串的存储结构 12.3 字符串的模式匹配算法 (1).朴素模式匹配算法 (2).KMP算法 i.next[]数组的求解 ii.next[]数组的优化——nextval数组 iii.手算nextval数组 iiii.机算nextval数组 + KMP函数 12.串 串,即字符串(string),由零个或多…...

Python爬取高清美女图片
文章概述 本文将详细介绍如何使用Python编写一个简单的爬虫来抓取高清美女图片。我们将利用requests库来发送HTTP请求,使用BeautifulSoup库来解析HTML文档,从而提取出图片的URL并将其下载到本地。 技术栈 Python: 编程语言requests: HTTP客户端库Beau…...

gin路由
1主文件 package main import ("github.com/gin-gonic/gin""godade/user""net/http" ) func main() {router : gin.Default()router.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "Hello World")})v1 : router…...

达梦数据库操作以及报错修改
执行失败(语句1) -6105:: 数据类型不匹配 第12 行附近出现错误 插入sql语句 INSERT INTO "by_ioc_rbac"."user_info" ("user_account", "user_name", "birthday", "password", "gender", "mobi…...

江科大/江协科技 STM32学习笔记P21
文章目录 ADC模数转换器ADC简介逐次逼近型ADCSTM32的ADCADC基本结构输入通道转换模式单次转换,非扫描模式连续转换,非扫描模式单次转换,扫描模式连续转换,扫描模式 触发控制数据对齐转换时间校准硬件电路电位器产生可调电压的电路…...

第三方jar自带logback导致本地日志文件不生成
1.问题及解决 这是依赖的jar包,自己有logback,只打印到控制台,导致我们项目里配置的error级别日志不会生成到日志文件中去。ai给的答案是自己控制加载顺序,但很麻烦,--logging.config也不行,最好下了个7z压…...

国产数据库备份恢复实现
数据库备份恢复是数据库高可用的基本能力,如何通过备份数据快速高效的恢复业务并且满足不同场景下的恢复需求,是各数据库厂商需要关注的要点。本文将介绍几种国产数据库的备份恢复功能,以加深了解。 1、数据库备份恢复方案 数据库备份是生产…...

数据仓库: 2- 数据建模
目录 2- 数据建模2.1 维度建模2.1.1 维度建模的基本概念2.1.1.1 事实表 (Fact Table)2.1.1.2 维度表 (Dimension Table)2.1.1.3 维度 (Dimension)2.1.1.4 度量 (Measure) 2.1.2 维度建模的主要模型2.1.2.1 星型模型 (Star Schema)2.1.2.2 雪花模型 (Snowflake Schema)2.1.2.3 星…...

Tomcat 漏洞
一.CVE-2017-12615 1.使用burp抓包 把get改成put jsp文件后加/ 添加完成后访问 木马 然后木马的网址 在哥斯拉测试并且添加 添加成功 然后我们就成功进去啦、 二.弱口令 点击后输入默认用户名、密码:tomcat/tomcat 登陆之后上传一个jsp文件 后缀改成war 然后访问我…...

分布式消息队列Kafka
分布式消息队列Kafka 简介: Kafka 是一个分布式消息队列系统,用于处理实时数据流。消息按照主题(Topic)进行分类存储,发送消息的实体称为 Producer,接收消息的实体称为 Consumer。Kafka 集群由多个 Kafka 实…...

C# Unity 面向对象补全计划 七大原则 之 迪米特法则(Law Of Demeter )难度:☆☆☆ 总结:直取蜀汉
本文仅作学习笔记与交流,不作任何商业用途,作者能力有限,如有不足还请斧正 本系列作为七大原则和设计模式的进阶知识,看不懂没关系 请看专栏:http://t.csdnimg.cn/mIitr,查漏补缺 1.迪米特法则(…...