【封装那些事】 缺失封装
缺失封装
没有将实现变化封装在抽象和层次结构中时,将导致这种坏味。
表现形式通常如下:
- 客户程序与其需要的服务变种紧密耦合,每当需要支持新变种或修改既有变种时,都将影响客户程序。
- 每当需要在层次结构中支持新变种时,都添加了大量不必要的类,这增加了设计的复杂度。
为什么不能缺失封装?
开闭原则(OCP)指出,类型应对扩展开放,对修改关闭。也就是说应该通过扩展(而不是修改)来改变类型的行为。没有在类型或层次结构中封装实现变化时,便违反了OCP。
缺失封装潜在的原因
未意识到关注点会不断变化
没有预测到关注点可能发生变化,进而没有在设计中正确封装这些关注点。
混合关注点
将彼此独立的各个关注点聚合在一个层次结构中,而不是分开时,如果关注点发生变化,可能导致类的数量呈爆炸式增长。
幼稚的设计决策
采用过于简单的方法,如为每种变化组合创建一个类时,可能导致设计无谓的复杂。
示例分析一
假设有一个Entryption类,它需要使用加密算法对数据进行加密。可供选择的加密算法有很多,包括DES(数据加密标准)、AES(高级加密标准)、TDES(三重数据加密标准)等。Entryption类使用DES算法对数据进行加密。
public class Encryption
{
/// <summary>
/// 使用DES算法进行加密
/// </summary>
public void Encrypt()
{
// 使用DES算法进行加密
}
}
假设出现了新需求,要求使用AES算法对数据进行加密。
最差的方案出现了:
public class Encryption
{
/// <summary>
/// 使用DES算法进行加密
/// </summary>
public void EncryptUsingDES()
{
// 使用DES算法进行加密
}
/// <summary>
/// 使用AES算法进行加密
/// </summary>
public void EncryptUsingAES()
{
// 使用AES算法进行加密
}
}
这种方案有很多不尽如人意的地方:
- Encryption类变得更大、更难以维护,因为它实现了多种加密算法,但是每次只使用一种。
- 难以添加新算法以及修改既有算法,因为加密算法是Encryption类不可分割的部分。
- 加密算法向Encryption类提供服务,但是与Encryption类紧紧耦合在一起,无法在其它地方重用。
不满意就重构,首先使用继承进行重构,会有2种方案可以选择:
选择1:
让Encryption类根据需求继承AESEncryptionAlgorithm或DESEncryptionAlgorithm类,并提供方法Encrypt()。这种方案带来的问题是Encryption类在编译阶段就将关联到特定的加密算法,更严重的是类之间的关系并不是is-a关系。
/// <summary>
/// AES算法加密类
/// </summary>
public class AESEncryptionAlgorithm
{
/// <summary>
/// 使用AES算法进行加密
/// </summary>
public void EncryptUsingAES()
{
// 使用AES算法进行加密
}
}
/// <summary>
/// DES算法加密类
/// </summary>
public class DESEncryptionAlgorithm
{
/// <summary>
/// 使用DES算法进行加密
/// </summary>
public void EncryptUsingDES()
{
// 使用DES算法进行加密
}
}
public class Encryption: AESEncryptionAlgorithm
{
/// <summary>
/// 使用算法进行加密
/// </summary>
public void Encrypt()
{
EncryptUsingAES();
}
}
选择2:
创建子类AESEncryption和DESEncryption,它们都扩展了Encryption类,并分别包含加密算法AES和DES的实现。客户程序可创建Encryption的引用,这些引用指向特定子类的对象。通过添加新的子类,很容易支持新的加密算法。但是这种方案的问题是AESEncryption和DESEncryption将继承Encryption类的其它方法,降低了加密算法的可重用性。
public abstract class Encryption
{
/// <summary>
/// 使用算法进行加密
/// </summary>
public abstract void Encrypt();
}
/// <summary>
/// AES算法加密类
/// </summary>
public class AESEncryption : Encryption
{
/// <summary>
/// 使用 AES算法进行加密
/// </summary>
public override void Encrypt()
{
// 使用 AES算法进行加密
}
}
/// <summary>
/// DES算法加密类
/// </summary>
public class DESEncryption : Encryption
{
/// <summary>
/// 使用 DES算法进行加密
/// </summary>
public override void Encrypt()
{
// 使用 DES算法进行加密
}
}
最佳的选择是使用策略模式:
- 可在运行阶段给Encryption对象配置特定的加密算法
- 可在其它地方重用层次结构EncryptionAlgorithm中定义的算法
- 很容易根据需要支持新的算法
/// <summary>
/// 算法加密接口
/// </summary>
public interface EncryptionAlgorithm
{
void Encrypt();
}
/// <summary>
/// DES算法加密类
/// </summary>
public class DESEncryptionAlgorithm : EncryptionAlgorithm
{
public void Encrypt()
{
//使用 DES算法进行加密
}
}
/// <summary>
/// AES算法加密类
/// </summary>
public class AESEncryptionAlgorithm : EncryptionAlgorithm
{
public void Encrypt()
{
//使用 AES算法进行加密
}
}
public class Encryption
{
private EncryptionAlgorithm algo;
public Encryption(EncryptionAlgorithm algo)
{
this.algo = algo;
}
/// <summary>
/// 使用算法进行加密
/// </summary>
public void Encrypt()
{
algo.Encrypt();
}
}
示例分析二
支持使用不同算法(DES和AES)对各种内容(Image和Text)进行加密的设计。
最简单最直观的的设计:
在这个设计中,有两个变化点:支持的内容类型和加密算法类型。对于这两个变化点的每种可能组合,都使用了一个类来表示。这样会有一个严重的问题:假设现在要求支持新加密算法TDES和新内容类型Data,类的数量呈爆炸性增长。因为变化点混在了一起,没有分别进行封装。
使用桥接模式进行封装:
使用桥接模式,分别封装这两个关注点的变化。现在要引入新内容类型Data和新加密算法TDES,只需要添加两个新类。既解决了类数量呈爆炸增长的问题,又增加了根为接口EncryptionAlgorithm层次结构中的加密算法的可重用性。
总结
- 不相关的关注点混在一起,抽象将变得难以重用。
- 对业务中可能的变化点,要给予扩展点,保证开闭原则(OCP),对扩展开放,对修改关闭。
参考:《软件设计重构》
- 我的WCF之旅 (11): 再谈WCF的双向通讯-基于Http的双向通讯 V.S. 基于TCP的双向通讯
- 我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案
- 扩展mysql - 手把手教你写udf
- scrapy初体验 - 安装遇到的坑及第一个范例
- Linux性能监控 - CPU、Memory、IO、Network
- 浅谈UDP(数据包长度,收包能力,丢包及进程结构选择)
- WCF版的PetShop之三:实现分布式的Membership和上下文传递
- “高并发”问题如何解决?腾讯云一分钟配置的“黑科技”帮您
- 初探JavaScript(三)——JS带我"碰壁"带我飞
- 初探JavaScript(四)——作用域链和声明提前
- 开发人员看测试之运行Github中的JBehave项目
- 如何高效地合并Spark社区PR到自己维护的分支
- 开发人员看测试之TDD和BDD
- AngularJS入门心得1——directive和controller如何通信
- 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 数组属性和方法
- 实用小技巧 | 用socket玩转http接口
- 深度学习之人脸识别模型--FaceNet
- Java常用的设计模式
- HW防守|应急溯源分析手册汇总篇
- Python进阶(一)
- Python版本的OpenCV安装
- CVE-2020-16875:Microsoft Exchange RCE复现
- 抢先学鸿蒙(HarmonyOS)2.0,你就是下一个大咖!
- Python高效编程之88条军规(2):你真的会格式化字符串吗?
- 搭建Typecho博客
- Python开发之Pandas的使用
- Python开发之numpy的使用
- flink sql实时计算当天pv写入mysql
- python爬取虎牙直播颜值区美女主播照片
- OpenCV图像处理笔记(三):霍夫变换、直方图、轮廓等综合应用