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

2023-11-02

我们都知道,类往往因为承担过多的责任而变得臃肿不堪。这种情况下,一般会使用"提炼类"这种手法将一部分责任分离出去。如果一个类变得"不负责任",一般会使用“内联类”这种手法将它融入另一个类。如果一个类使用了另一个类,一般会运用"隐藏委托关系"手法将这种关系隐藏起来通常是有帮助的。有时候隐藏委托关系会导致拥有者的接口经常性地变化,这时就可考虑使用"移除中间人"这种手法了。

Move Method

若发现程序中有个函数与其所驻类之外的另一个类进行更多交流。应该在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是将旧函数完全移除。
在这里插入图片描述
动机:(七年之痒)
"搬移函数"是重构理论中比较重要的特性之一。一般情况下,如果一个类有太多的行为,或者如果一个类与另一个类有太多合作而形成高度耦合,这时候就应该搬移函数。通过这种手段,可以使系统中的类更简单,这些类最终也将更干净利落地实现系统交付的任务。
在进行开发的过程中,我会时不时地浏览类的所有函数,从中寻找这样的函数:其使用另一个对象的次数要多于使用自己所驻对象的次数。一旦一些字段被移动,就应该进行这样的检查。一旦发现有可能搬移函数,就观察调用它的那一端、它调用的那一端,以及继承体系中的它的任何一个重定义的函数。然后根据“该函数与哪个读对象的交流比较多”来决定其移动路径。
事情往往不是那么容易做出决定。如果不能肯定是否应该移动一个函数,就应该继续观察其它函数。移动其它函数往往会让这项决定变得容易一些。有时候,即使移动了其它函数,还是很难对当前的函数做出决定。如果真的很难做出决定,那么或许"移动该函数与否"并不重要。那就,就让它呆在那儿,反正以后总是可以修改的。

范例
从一个表示"账户"的Account类开始:

class Account {
	private AccountType _type;
	private int _daysOverdrawn;
 
	double overdraftCharge() {
		if (_type.isPremium()) {
			double result = 10;
			if (_daysOverdrawn > 7)
				result += (_daysOverdrawn - 7) * 0.85;
			return result;
		} else {
			return _daysOverdrawn * 1.75;
		}
	}
}

现在假设有几种新的账户,每一种都有自己的“透支金额计费规则”,这样,我们就希望将overdraftCharge()函数搬移到AccountType类中。
首先,需要观察被overdraftCharge() 使用的每一项特征,考虑是否值得将它们与overdraftCharge()函数一起移动。在此例中,我们将_daysOverdrawn字段保留在Account类中,因为这个值会随着不同种类的账户而发生变化。然后,将overdraftCharge()函数复制到AccountType中,并做相应调整。


class AccountType {
	double overdraftCharge(int daysOverdrawn) {
		if (isPremium()) {
			double result = 10;
			if (daysOverdrawn > 7)
				result += (daysOverdrawn - 7) * 0.85;
			return result;
		} else {
			return daysOverdrawn * 1.75;
		}
	}
}

在例中,“调整”所表达的意思是:(1)对于使用AccountType特性的语句,去掉_type;(2)想办法得到仍然需要的Account类特性。当需要使用原类时,可有四种选择:(a)将这个特性同样移动到目标类,一般变为其成员变量;(b)建立或使用一个从目标类到原类的引用关系;(c)将原对象以参数传递给目标函数;(d)如果所需特性是变量,将它当作参数传递给目标函数。本例中是将_daysOverdrawn变量作为参数传递给目标函数。
调整目标函数后,编译,然后就可以将原函数的函数本体替换为一个简单的委托动作,然后编译并测试。

class Account {
	double overdraftCharge() {
		return _type.overdraftCharge(_daysOverdrawn);
}

Move Field

程序中某个字段被其所驻类之外的另一个类更多地用到。应该在目标类新建一个字段,修改原字段的所有用户,令它们改用新字段。
在这里插入图片描述
动机
如果发现,一个字段在其所驻类之外的另一个类有更多函数使用了它,就应该考虑搬移这个字段。所谓“使用”可能是通过设值/取值函数间接进行的。可能也会涉及移动该字段的用户(函数),这取决于是否需要保持接口不变化。如果这些函数看上去很适合待在原地,就选择搬移字段。
在使用“提炼函数”的时候,也可能需要搬移字段。这时会选择先搬移字段,然后搬移函数。
范例
从Account类开始:

class Account {
	private AccountType _type;
	private double _interestRate;
 
	double interestForAmount_days(double amount, int days) {
		return _interestRate * amount * days / 365;
	}
}

我想把表示利率的_interestRate搬移到AccountType类中。目前有一些函数引用了它,interestForAmount_days()就是其中之一。下一步需要在AccountType中建立_interestRate字段以及相应的访问函数:


class AccountType {
	private double _interestRate;
 
	void setInterestRate(double rate) {
		_interestRate = rate;
	}
 
	double getInterestRate() {
		return _interestRate;
	}
}

这时候就可以编译新的AccountType类了。
现在需要让Account类中访问_interestRate字段的函数转而使用AccoutType对象,然后删除Account类中的_interestRate字段。此时,必须删除原字段,才能保证其访问函数的确改变了操作对象,因为编译器会帮助我们的。


class Account {
	private AccountType _type;
 
	double interestForAmount_days(double amount, int days) {
		return _type.getInterestRate() * amount * days / 365;
	}
}

Extract Class

某个类做了应该由两个类做的事。应该建立一个新类,将相关的字段和函数从旧类搬移到新类。
在这里插入图片描述
动机
我们或多或少听过这样的教诲:一个类应该是一个清楚的抽象,处理一些明确的责任。但是,在实际工作中,类会不断成长扩展。你会在这儿加一些功能,在那儿加入一些数据。 当给类添加新责任时,就会觉得不值得为这项责任分理出一个单独的类。于是,随着责任的不断增加,这个类会变得过分复杂。很快,类就会变成一团乱麻,继而你就越发不想管理。
这样的类往往含有大量函数和数据,往往太大而不易理解。此时,就需考虑哪些部分是可以分离出去的,并将它们分离到一个单独的类中。如果某些数据和某些函数总是一起出现,某些数据经常同时变化甚至彼此相依,这就说明应该将其分离处理。一个比较有用的测试就是问问自己:如果搬移了某些字段和函数,会发生什么事情?其它字段和函数是否会变得无意义?
还有一个值得注意的地方是类的子类化方式。如果发现子类化只影响类的部分特性,或如果发现某些特性需要以一种方式来子类化,而另一些特性则需要以另一种方式子类化,则意味着需要分解原来的类。
范例

// 提炼类
	class Person {
		private String _name;
		private String _officeAreaCode;
		private String _officeNumber;
 
		public String get_name() {
			return _name;
		}
 
		public String getTelephoneNumber() {
			return ("(" + _officeAreaCode + ")" + _officeNumber);
		}
 
		public String get_officeAreaCode() {
			return _officeAreaCode;
		}
 
		public void set_officeAreaCode(String areaCode) {
			_officeAreaCode = areaCode;
		}
 
		public String get_officeNumber() {
			return _officeNumber;
		}
 
		public void set_officeNumber(String number) {
			_officeNumber = number;
		}
	}

在这个例子中,可以将与电话号码相关的行为分离到一个独立类中。首先需要定义一个TelephoneNumber类来表示“电话号码”这个概念:

class TelephoneNumber {
		private String _areaCode;
		private String _number;
 
		public String get_AreaCode() {
			return _areaCode;
		}
 
		public String getTelephoneNumber() {
			return ("(" + _areaCode + ")" + _number);
		}
 
		public void set_AreaCode(String areaCode) {
			_areaCode = areaCode;
		}
 
		public String getNumber() {
			return _number;
		}
 
		public void setNumber(String number) {
			_number = number;
		}
	}
class Person {
		//......
		private TelephoneNumber _officeTelephone = new TelephoneNumber();
		private String _name;
 
		public String get_name() {
			return _name;
		}
 
		public String getTelephoneNumber() {
			return _officeTelephone.getTelephoneNumber();
		}
 
		public TelephoneNumber getOfficeTelephone() {
			return _officeTelephone;
		}

}

下一步要决定是否对用户公开这个新类?可以将Person中与电话号码相关的函数委托至TelephoneNumber,从而完全隐藏这个新类;也可以直接将它对用户公开。也可以将它公开给部分用户(位于同一个包中的用户),而不公开给其它用户。

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

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

  • vue pdf.js统计pdf的页数

    参考链接作者原文展示了PDF 我只需要一部分功能
  • Adobe进军AI第一步——Firefly试用体验

    在关于人工智能讨论度高居不下的今天 各个行业的领路企业也纷纷不甘落后 Adobe作为媒体界的行业标杆 就在近期推出了自己的人工智能图像应用 萤火虫firefly 虽然这只萤火虫刚刚 起飞 它已经展现的文字生图和能力算是及格 我分别在网页版和
  • spark-submit 碰到 Spark-submit:System memory 466092032 must be at least 471859200

    在利用spark进行分布式计算时 home hadoop spark spark 2 4 0 bin hadoop2 7 bin spark submit master yarn ALS py 以上代码是在centos7 利用spark集群
  • vim 一段代码整体移动

    方法1 可以用ctrl v 然后上下移动光标 再shift i进入编辑模式 然后按删除或者空格或者tab键来移动第一行 然后按ESC 就能整段代码动起来了 方法2 1 点击 esc 键进入命令模式 使用 set nu 显示行号 2 点击 e
  • 又是第一!GBASE南大通用蝉联中国分析型数据库管理系统市场TOP1

    报告指出 大数据时代 用户对数据分析的需求不断提升 希望从大量数据中获得新的数据价值 数据分析需求不断上升 分析型数据库市场保持稳定增长 GBASE南大通用作为分析型数据库市场的代表企业 位居本土厂商第一名 在分析型数据库市场 GBASE自

随机推荐

  • 插件分享

    前言 要问我Goby怎么样 我会坚定回答你 最强实时网络空间测绘 没有之一 初次发现Goby还是来自于同事 hq404的推荐 看完第一反应 真漂亮 我馋了 我要xxxxxx 其Logo和UI做的相当棒 当然不仅拥有华丽的外表 更让我深爱又离
  • python爬取新发地菜价

    import requests from bs4 import BeautifulSoup import csv url http www xinfadi com cn marketanalysis 0 list 1 shtml respo
  • 【机试练习】【C++】【PAT A1053】Path of Equal Weight(玄学一样的“段错误”)

    此题有较大的玄学 如果将cmp函数的默认返回值更改为true 则会出现最后一个测试用例的 段错误 在代码中以 我的天 玄学 标识出 include
  • Java阻塞队列

    目录 一 阻塞队列的特点 二 生产者 消费者 存在问题 三 阻塞队列 Java实现 属性 方法 put方法 生产者 线程专门调用的方法 get方法 消费者 线程专门调用的方法 执行顺序分析 图解 在我们上图的代码当中 如果把while改成i
  • Sharding-JDBC(八)5.3 系列升级解读

    目录 一 背景 二 影响范围 1 Maven 坐标调整 2 自定义算法调整 3 事务调整 4 配置文件调整 三 升级指导 1 新的 ShardingSphereDriver 数据库驱动 2 正在使用 Spring Boot Starter
  • 2023华为OD机试真题【找朋友/单调栈】

    题目描述 在学校中 N个小朋友站成一队 第i个小朋友的身高为height i 第i个小朋友可以看到的第一个比自己身高更高的小朋友j 那么j是i的好朋友 要求j gt i 请重新生成一个列表 对应位置的输出是每个小朋友的好朋友位置 如果没有看
  • python爬虫系列5--xpath

    教程地址 http www runoob com xpath xpath tutorial html XPath在python的爬虫学习中 起着举足轻重的地位 对比正则表达式re两者可以完成同样的工作 实现的功能也差不多 但XPath明显比
  • 用 STM32 通用定时器做微秒延时函数(STM32CubeMX版本)

    概述 在使用 DHT11 的时候 时序通信需要微秒来操作 STM32CubeMX 自带一个系统时钟 但是实现的是毫秒级别的 因此就自己用通用计时器实现一个 文章目录 概述 1 配置定时器时钟 2 计数器时钟频率及计数模式 预分频系数 计数器
  • tomcat调优的几个方面

    和早期版本相比最新的Tomcat提供更好的性能和稳定性 所以一直使用最新的Tomcat版本 现在本文使用下面几步来提高Tomcat服务器的性能 增加JVM堆内存大小 修复JRE内存泄漏 线程池设置 压缩 数据库性能调优 Tomcat本地库
  • css画间距可控制的虚线

    借助linear gradient dash div margin left 50px margin right 50px height 10px background linear gradient to left transparent
  • linux git代码明明是最新版本的,status为啥全是modified?

    解决办法 依次执行以下两句代码 git rm cached r git reset hard
  • 使用 PyTorch 对自定义数据集进行二分类(基于Vision Transformer)

    内容 简短描述 ViT 的简短描述 编码部分 使用 ViT 对自定义数据集进行二分类 附录 ViT hypermeters 解释 简短描述 视觉转换器是深度学习领域中流行的转换器之一 在视觉转换器出现之前 我们不得不在计算机视觉中使用卷积神
  • 【Python】turtle海龟画图练习

    Turtle 方法查看 turtle 海龟绘图 同心圆 import turtle i 1 r 0 while i lt 6 r 30 自己设 turtle circle r 画个圆 turtle penup 起笔 turtle sety
  • 前后端RSA加解密

    前端vue RSA加密 一 安装 npm install jsencrypt save dev 二 创建js文件 在src目录下创建util文件夹 然后在util文件夹下创建 security js 文件 1 引入jsencrypt 引入加
  • Java数据类型转换

    1 基本数据类型 byte short char int long float double boolean 2 引用类型数据 String 枚举 数组 接口 枚举 3 基本数据和引用类型数据的区别 1 基本数据类型变量 存的是值的本身 2
  • APP从苹果开发者A账号转移到B账号的流程

    今天把公司的一个APP从苹果开发者A账号转移到B账号 在这里记录具体操作流程 准备好开发者账号A APP所在的原账号 开发者账号B APP迁移目标账号 登录A账号 选择 App Store Connect 点击 Go to App Stor
  • Linux相关关机命令及服务器关机后如何进行开机操作

    linux一般用在服务器上 很少遇到关机的情况 毕竟关机服务就会中断 除非特殊情况不得已才会关闭 正确的关机流程 sync gt shutdown或reboot或halt 无论重启还是关机 都需要先sync将内存数据同步到硬盘中 避免数据丢
  • 抽象数据类型Polynomial 的实现(第二章 P40-43 算法2.22,2.23)

    抽象数据类型Polynomial 的实现 多项式的加法 乘法 typedef int Status Status是函数的类型 其值是函数结果状态代码 如OK等 typedef int Boolean Boolean是布尔类型 其值是TRUE
  • vue关于json数据格式的展示<pre>标签的使用

    起因 需要把字符串按json格式展示到页面上 直接展示或者利用JSON parse 展示页面上数据都会挤成一坨 解决方法 利用
  • 重构——在对象之间搬移特性(1)

    我们都知道 类往往因为承担过多的责任而变得臃肿不堪 这种情况下 一般会使用 提炼类 这种手法将一部分责任分离出去 如果一个类变得 不负责任 一般会使用 内联类 这种手法将它融入另一个类 如果一个类使用了另一个类 一般会运用 隐藏委托关系 手