重构——在对象之间搬移特性(2)

2023-11-17

Inline Class

某个类并没有做太多的事情。应该将这个类的所有特性搬移到另一个类中,然后移除原类。

过程与Extract Class相反,不再做介绍。

Hide Delegate

客户通过一个委托关系来调用另一个对象。应当在服务类上建立客户所需的所有函数,用以隐藏委托关系。
在这里插入图片描述
动机
我们都知道,”封装“即使不是对象的最为关键的特征,也是最为关键的特征之一。 ”封装“意味着每个对象都应该尽可能少了解系统的其它部分。这样,一旦发生变化,需要了解这一变化的对象就会比较少——这会使变化比较容易。
如果某个客户先通过服务对象的字段得到了另一个对象,然后调用后者的函数,那么客户就必须知晓这一层委托关系。万一委托关系发生了变化,那么客户也得相应改变。那么,我们可以再服务器对象上放置一个简单的委托函数,将委托关系隐藏起来,从而去除这种依赖。这样,即使在将来委托关系发生变化,也只在服务器对象中,而不会涉及客户。 简而言之,就是多加一个中介。
在这里插入图片描述
范例

class Person{
	Department _department;
 
	public Department get_department() {
		return _department;
	}
	public void set_department(Department _department) {
		this._department = _department;
	}
}
class Department{
	private String _changeCode;
	private Person _manager;
	
	public Person get_manager() {
		return _manager;
	}
	public void set_manager(Person _manager) {
		this._manager = _manager;
	}
	//......
}

如果客户想要知道某人的经理是谁,他必须先取得Department对象:

		Person john = new Person();
		Person manager = john.get_department().get_manager();

这样的编码对客户暴露了Department的工作原理,于是客户知道;Department用以追踪“经理”这条信息。如果对客户隐藏Department,可以减少耦合。为了到达预期的目的,在Person中建立一个简单的委托函数:

public Person getManager(){
		return _department.get_manager();
	}

现在,需要修改Person的所有用户,让它们使用新的函数:

Person manager = john.getManager();

Remove Middle Man

某个类做了过多的简单委托动作。应当让客户直接调用受托类。
与前者逆过程,不介绍。

Introduce Foreign Method

你需要为提供服务的类增加一个函数,但你无法修改这个类。应该在客户类中建立一个函数,并以第一参数形式传入一个服务器类实例。

//before
	Date newStart = new Date(previous.getYear(),
			previous.getMonth(),previous.getDate() + 1);

?

//after
	Date newStart = nextDay(previous);
	
	private static Date nextDay(Date date){
		//......
		return new Date(date.getYear(),date.getMonth(),date.getDate() + 1)
	}

动机
你经常会遇到这样的事情:你正在使用一个类,它很好,为你提供了很多需要的服务。而后,你又需要增加一项新服务,这个类却无法供应。于是你开始咒骂:为什么不能做这件事情呢?如果可以修改源码,你便可以自行增加一个新函数;如果不能,你就得在客户端编码,补足你要的那个函数。
如果只需要使用这个功能一次,那么额外的编码也没什么大不了的,甚至可能不需要原本提供服务的那个类。然而,如果需要多次使用这个函数,就不得不重复代码了。切记:重复代码是软件万恶之源。这些重复的代码应该被抽取出来放到同一个函数中。进行该项重构时,如果以外加函数实现一项功能,那就是一个明确信号:这个函数原本应该在提供服务的类中实现。
如果发现为一个服务类建立了大量外加函数,或者发现有许多类都需要同样的外加函数,就不应该再使用本项重构,而应该使用“引入本地扩展”。但是不要忘记:外加函数终归是权宜之计。如果有可能的话,应该将这些函数搬移到它们的理想家园。由于代码所有权的原因使你无法做这样的搬移,就把外加函数交给服务类,请它帮忙在服务类中实现这个函数。

Introduce Local Extension

你需要为服务类提供一些额外函数,但你无法修改这个类。应该建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或者包装类。
在这里插入图片描述
动机
我们都无法预知一个类的未来,它们常常无法为你预先准备一些有用的函数。如果可以修改源码,那就太好了,那样就可以直接加入自己需要的函数。但是你经常无法修改源码。如果只是需要一两个函数,可以引入外加函数进行处理。但如果需要多个函数,外加函数就很难控制它们了。所以,需要将这这些函数组织起来,放到一个恰当的地方去。要达到这样的目的,需要用到子类化和包装这两种技术。这种情况下,把子类或包装类统称为本地扩展。
本地扩展是一个独立的类,但也是被扩展的子类型:它提供类的一切资源特性,同时额外添加新特性。在任何使用源类的地方,你都可以使用本地扩展取而代之。
使用本地扩展使得以坚持“函数和数据应该被统一封装”的原则。如果你一直把本该放在扩展类中的代码零散地放置于其它类中,最终只会让其它类变得复杂,并使得其中函数难以被复用。
在子类和包装类之间做选择,通常会选择子类,因为这样的工作量比较小。但是,制作子类的最大障碍在于,它必须在对象创建初期实施。如果可以接管对象的创建过程,那当然没问题;但如果你想在对象创建之后再使用本地扩展,就会有问题。此外,子类化方案还必须产生一个子类对象,这样如果有其它对象引用了旧对象,就同时有两个对象保存了原数据!如果原数据不可修改,那可以放心复制;但是如果允许修改,问题就随之而来,因为一个修改动作无法同时改变两份副本。这时就必须改用包装类。使用包装类时,对本地扩展的修改会波及原对象,反之也成立。
范例
我们以JAVA中的Date类为例。Java已经提供了我们想要的功能,但是在到来之前,很多时候需要扩展Date类。
第一件需要做的事情就是:使用子类还是包装类。子类化是比较显而易见的方法:

class MyDateSub extends Date{
	public MyDateSub nextDay()...
	public int dayOfYear()...
}

包装类则需要用上委托:

class MyDateWrap{
	private Date _original;
	
}     

1:使用子类
首先,要建立一个MfDateSub类来表示“日期”,并使其成为Date的子类:

 class MyDateSub extends Date

然后,需要处理Date和扩展类之间的不同处。MfDateSub构造函数需要委托给Date构造函数:

public MyDateSub(String dateStr){
	super(dateStr);
}

现在,需要加入一个转型构造函数,其参数是一个源类的对象:

public MyDateSub(Date arg){
	super(arg.getTime());
}

现在,可以再扩展类中添加新特性,并使用搬移函数将所有的外加函数搬移到扩展类。于是:

client class...
	private static Date nextDay(Date arg){
	// foreign method, should be on date
	return new Date(arg.getYear(),arg.getMonth(),arg.getDate()+1);
}

经过搬移之后,就变成:

class MyDateSub...
	Date nextDay(){
		return new Date(getYear(),getMonth(),getDate()+1);
}

2:使用包装类
首先声明一个包装类,使用包装类时,对构造函数的设定与先前有所不同。现在的构造函数将只执行一个单纯的委托动作:

class MyDateWrap{
	private Date _original;
}
public MyDateWrap(String dateStr){
	_original = new Date(dateStr);
}

而转型构造函数则只是对其实例变量赋值而已:

public MyDateWrap(Date arg){
	__original = arg;
}

接下来是一项枯燥乏味的工作:为原始类的所有函数提供委托函数。此处只展示两个函数:

public int getYear(){
	return original.getYear();
}
 
public boolean equals(Object arg){
	if(this==arg){
		return true;
	}
	if(!(arg instanceof MyDateWrap )){
		return false;
	}
	MyDateWrap other = (MyDateWrap)arg;
	return (_original.equals(other._original));
}

完成这项工作之后,可以使用搬移函数将日期相关行为搬移到新类中。于是:

client class...
	private static Date nextDay(Date arg){
	// foreign method, should be on date
	return new Date(arg.getYear(),arg.getMonth(),arg.getDate()+1);
}

经过搬移之后,有:

class MyDateWrap...
	Date nextDay(){
		return new Date(getYear(),getMonth(),getDate()+1);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

重构——在对象之间搬移特性(2) 的相关文章

  • 【ESP-IDF】2.ESP32C3移植u8g2显示库驱动OLED

    前言 这个系列的文章属于是为了一碟醋包了一顿饺子系列 起因是看到tb上某家店的ESP32C3开发板才9 9包邮 想着研究一下 把手头有个用Arduino UNO实现的项目升级一下 于是就有了这个系列 ESP32C3的简介 2020 年末 乐
  • React Navigation(三)-StackActions(API)

    原文链接 StackActions对象包含了生成特定actions的方法 即基于栈导航器的actions 这些方法扩展了NavigationActions 支持以下actions Reset 用一个新的状态替换当前状态 Replace 用其
  • Python 人脸表情识别

    人脸表情识别 一 图片预处理 二 数据集划分 三 识别笑脸 四 Dlib提取人脸特征识别笑脸和非笑脸 参考 环境搭建可查看Python人脸识别微笑检测 数据集可在https inc ucsd edu mplab wordpress inde
  • 阿里云CDN缓存预热与刷新以及常见的故障汇总

    文章目录 1 为CDN缓存的文件增加过期时间 2 CDN缓存预热配置 3 CDN缓存刷新配置 4 常见故障 CDN缓存预热指的是主动将要缓存的文件推送到全国各地的CDN边缘加速器上 减少回源率 提供命中率 缓存刷新指的是后期上传了同名的文件
  • ubuntu9.10 虚拟机连接windows网络上网,以及NFS挂载网络设置。

    1 虚拟机设置 2 关掉网卡 sudo ifconfig ethxx down 3 打开网卡 sudo ifconfig ethxx up 4 打开浏览器就可以使用网络上网了 NFS 1 vmware软件设置网络连接方式 2 选择桥接方式

随机推荐

  • 写了placement new也要写placement delete——条款52

    placement new和placement delete并非C 兽栏中最常见的动物 如果你不熟悉它们 不要感到挫折或忧虑 回忆条款16和17 当你写一个new表达式像这样 Widget pw new Widget 共有两个函数被调用 一
  • 映射表原理分析与总结

    在使用本地缓存时 经常用到映射表 大家都知道映射表保存数据的原理是将key做hash再取余 余数落在数组的不同索引中 利用数组的索引获取元素 时间复杂度为O 1 这样查询速度很快了 但是也存在一个问题 那就是如果两个key落到同一个索引桶上
  • 使用js获取上传文件的真实路径

    我们在使用html中的
  • 闭关之现代 C++ 笔记汇总(二):特性演化

    目录 前言 C 98 C 98 之前 C 98 的主要语言特性 特性总结 dynamic cast RAII 标准库组件 总结 find if 其他语言对 C 影响 非 C 98 内容 C 对其他语言影响 非 C 98 内容 C 11 C
  • java jre jvm_JVM、JRE和JDK的关系

    JVM Java Virtual Machine是Java虚拟机 Java程序需要运行在虚拟机上 不同的平台有自己的虚拟机 因此Java语言可以实现跨平台 JRE Java Runtime Environment包括Java虚拟机和Java
  • 并发问题(二)什么是并发

    1 什么是并发操作 并发操作是指同一时间可能有多个用户对同一数据进行读写操作 2 并发操作对数据的影响 如果对并发操作不做任何控制的话 会造成数据的不完整性 可能造成读脏数据 不可重复读 丢失修改还有幻读 3 对数据不完整性的举例 1 丢失
  • Java Spring Boot 框架

    Java Spring Boot 框架 Spring Boot是一个用于快速构建独立 生产级别的Java应用程序的开源框架 它是Spring Framework的扩展 旨在简化Spring应用程序的开发和部署 并提供一个约定优于配置的开发模
  • MYSQL5.1 WINDOWS环境下导出查询数据到EXCEL文件

    今天做一个多表的联合查询 用myadmin不支持导出 于是找到下面的方法 不错 查询出来的记录 导出到EXCEL文件 直接做报表输出 测试环境WINDOWS XP OFFICE 2003 MYSQL 5 1 451 创建一个测试表 3个字段
  • 利用gcc-arm-none-eabi开源工具链开发STM32程序

    一 前言 入门STM32开发时 用的是keil 这个IDE 后面因为要提高开发效率和keil 版权问题 选择开源的arm none eabi gcc 通过命令行调用make工具进行编译 链接 烧录 打包 二 要达到的效果 2 1 编译STM
  • 关于 C++ 打印 PDF 打印及 PDF 转图片、合并

    原文 http www aqcoder com post content id 42 pdf Portable Document Format 的简称 意为 便携式文档格式 是由 Adobe Systems 用于与应用程序 操作系统 硬件无
  • 从傅里叶变换看seq2seq

    通常使用循环神经网络处理NLP 自然语言处理 问题 循环神经网络模型特点决定了输出与输入维度不同 但数量相同 这显然有违常识 比如分词后中文句子 我 去 上班 翻译成英文后是 i am going to work 源语言与目标语言在表达同一
  • element ui下拉框的使用

    type label 支部资讯 value 1 label 违规公示 value 2
  • 12.完善统计图形——调整刻度范围和刻度标签

    文章目录 1 调整刻度范围和刻度标签 xlim 和xticks 2 逆序设置坐标轴刻度标签 刻度范围是绘图区域中坐标轴的取值区间 包括x轴和y轴的取值区间 刻度范围是否合适直接决定绘图区域中图形展示效果的优劣 因此 调整刻度范围对可视化效果
  • go添加国内镜像加速

    添加国内镜像加速 七牛云 七牛云镜像 全球CDN加速 全球CDN加速 打开你的命令终端输入Go 1 13 及以上 推荐 go env w GO111MODULE on go env w GOPROXY https goproxy cn di
  • 拔剑四顾心茫然,绿源直呼“行路难”

    老牌两轮电动车品牌绿源上市之旅 多歧路 6月7日 北京市市场监督管理局公布北京市电动自行车产品质量监督抽查结果 绿源两款电动自行车因存在问题被点名 充电器和蓄电池 整车质量 控制系统等不符合标准 而就在一周多以前 绿源还向港交所第二次递交了
  • linux 常用系统命令

    1 调出登录主机列表 sshgo 2 查找服务器 server name 3 切换 deploy 用户 sudo su deploy 4 上传本地文件 rz be 5 下载文件 sz filename 6 crontab CentOS 6
  • 这台计算机无法连接到服务器,请确认网络连接是否正常,Win7玩英雄联盟提示“无法连接到服务器,请检查您的网络连接”六种解决方法...

    说到LOL英雄联盟相信很多玩家都比较熟悉了 它是一款网络游戏 但是最近有用户说Win7系统玩英雄联盟的时候提示 连接失败 无法连接到服务器 请检查您的网络连接 如下图所示 导致游戏无法顺利进行 怎么办呢 下面小编给大家分享Win7玩英雄联盟
  • Shell脚本之数字大小排列(小到大)

    脚本内容 bin bash read p 请输入一个数字 num1 read p 请输入一个数字 num2 read p 请输入一个数字 num3 tmp 0 如果 num1 大于 num2 就把 num1 和和 num2 的值对调 确保
  • defineProperty和proxy区别

    1 不同点 区别一 defineProperty 是对属性劫持 proxy 是对代理对象 如果需要监听某一个对象的所有属性 需要遍历对象的所有属性并对其进行劫持来进行监听 Object keys data forEach key gt le
  • 重构——在对象之间搬移特性(2)

    Inline Class 某个类并没有做太多的事情 应该将这个类的所有特性搬移到另一个类中 然后移除原类 过程与Extract Class相反 不再做介绍 Hide Delegate 客户通过一个委托关系来调用另一个对象 应当在服务类上建立