重构——在对象之间搬移特性
考虑家具的移动性,方便搬家或重新布置 #生活技巧# #居家生活技巧# #家具摆放技巧# #空间布局#
文章目录 楔子7.1 Move Method(搬移函数)动机做法范例 7.2 Move Field(搬移字段)动机做法范例:使用Self-Encapsulation 7.3 Extract Class 提炼类动机做法 7.4 Inline Class(将类内联化)7.5 Hide Delegate(隐藏“委托关系”)动机做法范例 7.6 Remove Middle Man(移除中间人)7.7 Introduce Foreign Method(引入外加函数)动机 7.8 Introduce Local Extension(引入本地扩展)动机做法范例范例:使用子类范例:使用包装类楔子
学习笔记 《重构,改善既有代码的设计》
类往往会因为承担过多责任而变得臃肿不堪。这种情况下,我们使用Extract Class将一部分责任分离出去。如果一个类变得太“不负责任”,我们就会使用Inline Class将它融入另一个类。如果一个了你使用了另一个类,运用Hide Delegate将这种关系隐藏起来通常是有帮助的。有时候隐藏委托会导致拥有者的接口经常变化,此时需要使用Remove Middle Man。
7.1 Move Method(搬移函数)
在该函数最常引用的类中建立一个有类似行为的新函数。将旧函数变成一个单纯的委托函数,或是将旧函数完全移除。
“搬移函数”是重构理论的支柱。如果一个类有太多的行为,或者如果一个类与另一个类有大多合作形成的高度耦合,我们就可以搬移函数。通过这种手段,可以使系统中的类更简单,这些类最终也将更干净利落地实现系统交付的任务。
我们常常会浏览类的所有函数,从中寻找这样的函数:使用另一个对象的次数比使用自己所驻对象的次数还多。一旦我们移动成功了一些字段,就该做这样的检查。一旦发现有可能搬移的函数,我们就会观察调用它的那一端、它调用的那一端。以及继承体系中它的任何一个重定义函数。然后,会根据“这个函数与哪个对象的交流比较多”,决定其移动路劲。
范例
用一个表示“账户”的Account类说明这个重构。
public class Account { double overdraftChange() { if (_type.isPremium()) { double result = 10; if (_daysOverdrawn > 7) { result += (_daysOverdrawn - 7) * 0.85; } return result; } else { return _daysOverdrawn * 1.75; } } private AccountType _type; private int _daysOverdrawn; }
1234567891011121314151617假设有几种新账户,每一种都有自己的“透支金额计费规则”。我们希望将overdraftChange搬移到AccountType类中去。
第一步要做的是:观察被overdraftChange使用的每一项特性,考虑是否值得他们与overdraftChange一起移动。次例中我们需要让_daysOverdrawn字段保留在A从count类中,因为这个值会随着不同账户而变化。然后,我们将overdraftChange函数复制到AccountType中,并做相应的调整。
public class AccountType { double overdraftChange(int daysOverdrawn) { if (isPremium()) { double result = 10; if (daysOverdrawn > 7) { result += (daysOverdrawn - 7) * 0.85; } return result; } else { return daysOverdrawn * 1.75; } } } 123456789101112131415
这个例子中,“调整”的意思是:1 对于使用AccouontType 特性的语句,去掉_type 。2 想办法得到依旧需要的Account类特性。当我需要使用源类的特性时,有4中选择。① 将这个特性也移动到目标类 ② 建立或使用一个从目标类到源类的引用关系 ③ 将源对象当做参数传给目标函数。 ④ 如果所需特性是个变量。将它当做参数传给目标函数。
本例中,我们将 **_daysOverdrawn **变量作为参数传给目标函数。
public class Account { double overdraftChange() { return _type.overdraftChange(_daysOverdrawn); } private AccountType _type; private int _daysOverdrawn; } 12345678
我们可以保留代码如今的样子,也可以删除源函数,如果决定删除,就得找出源函数的所有调用者,并将这些调用重新定向。该为调用Account 的bankCharge()。 1
public class Account { double bankCharge() { double result = 4.5; if (_daysOverdrawn > 0) { result += _type.overdraftChange(_daysOverdrawn); } return result; } } 12345678910
所有的调用点都修改完毕后,就可以删除源函数在Account中的声明了。我们可以在每次删除之后编译并测试,也可以一次性批量完成。如果被搬移的函数不是private的,我们还需要检查其他类是否使用了这个函数。<br />次例之中被搬移函数只引用了一个字段,所以只需要将这个字段作为参数传给目标函数就行了。如果被搬移函数调用了Account中的另一个函数,我们就不能这么简单地处理,。这种情况下必须将源对象传递给目标函数。 1
public class AccountType { double overdraftChange(Account account) { if (isPremium()) { double result = 10; if (account.get_daysOverdrawn() > 7) { result += (account.get_daysOverdrawn() - 7) * 0.85; } return result; } else { return account.get_daysOverdrawn() * 1.75; } } } 1234567891011121314
如果需要源类的多个特性,那么我们也会将源对象传递给目标函数。不过如果目标函数需要太多源类特征,就得进一步重构。通常情况下,我们会分解目标函数,并将其中一部分移回源类。
7.2 Move Field(搬移字段)
在程序中,某个字段被其所驻类之外的另一个类更多地用到。在目标类新建一个字段,修改源字段的所有用户,令它们改用新字段。
在类之间移动状态和行为,是重构过程中必不可少的措施。随着系统的发展,你会发现自己需要新的类,并需要将现有的工作责任拖到新的类中。
如果我们发现。对于一个字段,在其所驻类之外的另一个类中有更多函数使用了它,可以考虑搬移这个字段。
### 范例 下面是Account类的部分代码。 ```java public class Account { private AccountType _type; private double _interestRate;
double interestForAmount_days(double amount, int days) { return _interestRate * amount * days / 365; } 123
}
想把表示利率的 ` **_interestRate**` 搬到 ` AccountType` 类去。目前已有数个函数引用了它,`interestForAmount_days` 就是其中之一。下一步要在 `AccountType ` 中建立 `**_interestRate**` 字段以及相应的访问函数。 ```java public class AccountType { private double _interestRate; public double get_interestRate() { return _interestRate; } public void set_interestRate(double _interestRate) { this._interestRate = _interestRate; } } 1234567891011121314
现在,我们需要让Account类中访问 **_interestRate** 字段的函数转移而使用 AccountType对象。然后删除Account类中的 **_interestRate**。
public class Account { private AccountType _type; double interestForAmount_days(double amount, int days) { return _type.get_interestRate() * amount * days / 365; } } 1234567 范例:使用Self-Encapsulation
如果有很多函数已经使用了 **_interestRate** 字段,我们该先运用 SelfEncapsulateField (自我封装)
public class Account { private AccountType _type; private double _interestRate; double interestForAmount_days(double amount, int days) { return getTnterestRate() * amount * days / 365; } public void set_interestRate(double arg) { this._interestRate = arg; } public double getTnterestRate() { return _interestRate; } } 1234567891011121314
这样,搬移字段后,我们就只需要修改访问函数 1
public class Account { private AccountType _type; double interestForAmount_days(double amount, int days) { return getTnterestRate() * amount * days / 365; } public void set_interestRate(double arg) { _type.set_interestRate(arg); } public double getTnterestRate() { return _type.get_interestRate(); } } 12345678910111213
7.3 Extract Class 提炼类
某个类做了应该由两个类做的事。建立一个新类,将相关的字段和函数从旧类搬移到新类。
一个类应该是一个清楚的抽象。处理一个明确的责任。但是在实际工作中,类会不断成长扩展。你会在这儿加入一些功能,在那儿加入一些数据。给某个类添加一项新责任时,你会觉得不值得为这项责任分离出一个单独的类。于是,随着责任不断增加,这类变得过分复杂。很快,你的类就变成一团乱麻。
这样的类往往含有大量函数和数据。这样的类往往大二不易理解。此时你需要考虑那些部分可以分离出去。并将他们分离到一个单独的类中。如果某些数据和某些函数总是一起出现,某些数据经常同时变化甚至彼此相依,这就表示你应该将它们分离出去。
另一个往往在开发后期出现的型号是类的子类化方式,如果你发现出现了子类化只影响类的部分特性,或如果你发现某些特性需要以一种方式来子类化,某些特性需要以另一种方式子类化,这就意味着你需要分解原来的类。
### 范例 从一个简单的Person开始。 ```java public class Person { public String get_name() { return _name; } public String getTelephoneNumber(){ return ("("+_officeAreaCode+")"+_officeNumber); } public void set_officeAreaCode(String _officeAreaCode) { this._officeAreaCode = _officeAreaCode; } public String get_officeNumber() { return _officeNumber; } public void set_officeNumber(String _officeNumber) { this._officeNumber = _officeNumber; } private String _name; private String _officeAreaCode; private String _officeNumber; }
这个例子中,我们可以将与电话号码相关的行为分离到一个独立类中。<br />先建立从Phone到TelephoneNumber的连接 ```java class Person private TelephoneNumber _officeTelepone=new TelephoneNumber(); 1234
先移动一个字段 1
public class TelephoneNumber { public String getAreaCode() { return _areaCode; } public void setAreaCode(String _areaCode) { this._areaCode = _areaCode; } private String _areaCode; } public class Person { private TelephoneNumber _officeTelepone = new TelephoneNumber(); public String get_name() { return _name; } public String getTelephoneNumber() { return ("(" + getOfficeAreaCode() + ")" + _officeNumber); } String getOfficeAreaCode() { return _officeTelepone.getAreaCode(); } public String get_officeNumber() { return _officeNumber; } public void set_officeNumber(String _officeNumber) { this._officeNumber = _officeNumber; } private String _name; private String _officeNumber; }
12345678910111213141516171819202122232425262728293031323334然后移动其他字段 1
public class Person { public String get_name() { return _name; } public String getTelephoneNumber() { return _officeTelepone.getTelephoneNumber(); } TelephoneNumber get_officeTelepone() { return _officeTelepone; } private TelephoneNumber _officeTelepone = new TelephoneNumber(); private String _name; } public class TelephoneNumber { public String getTelephoneNumber() { return ("(" + _areaCode + ")" + _number); } public String getAreaCode() { return _areaCode; } public void setAreaCode(String _areaCode) { this._areaCode = _areaCode; } public void set_number(String _number) { this._number = _number; } public String get_number() { return _number; } private String _areaCode; private String _number; }
12345678910111213141516171819202122232425262728293031323334353637387.4 Inline Class(将类内联化)
某个类没有做太多的事情。将这个类的所有特性搬移到另外一个类中,然后移除原类。
7.5 Hide Delegate(隐藏“委托关系”)
![重构.png](https://img-blog.csdnimg.cn/img_convert/077a6326aa3ffb63975dcfe071166858.png#clientId=ud9b6b387-1960-4&from=ui&id=ucb96630d&margin=[object Object]&name=重构.png&originHeight=322&originWidth=621&originalType=binary&ratio=1&size=18304&status=done&style=stroke&taskId=u99853e34-0fbe-4401-a292-7cd241b7514)
客户端通过一个委托函数来调用另一个对象。在服务类上建立客户所需要的所有函数,用以隐藏委托关系。
如果某个客户先通过服务对象得到另一个对象,然后调用后者的函数。那么客户就必须知晓这一层委托关系。万一委托关系发生变化,客户也得相应变化。可以在服务对象上放置一个简单的委托函数,将委托关系隐藏起来,从而除去这种依赖。这么一来,即便将来委托关系发生变化,变化也将被限制在服务对象内,不会涉及客户。
范例
public class Person { Department _department; public Department getDepartment() { return _department; } public void set_department(Department _department) { this._department = _department; } } class Department { private String _chargeCode; private Person _manger; public Person get_manger() { return _manger; } public Department(Person manger) { this._manger = manger; } }
12345678910111213141516171819202122232425如果客户希望知道某人的经历是谁,他必须先取得Department对象。
manager = john.getDepartment().get_manger(); 1
这样的编码就是对客户揭露了Department的工作原理,于是客户知道:Department用以追踪“经理”这条信息。如果对客户隐藏Department,可以减少耦合,为了这一目的,在Person中建立一个简单的委托函数。 1
Person getManager() { return _department.get_manger(); } 123
7.6 Remove Middle Man(移除中间人)
某个类做了过多的简单委托动作,让客户直接调用受委托。
7.7 Introduce Foreign Method(引入外加函数)
需要为提供服务的类增加一个函数,但无法修改这个类。在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。
Date newStart = new Date(previousEnd.getYear(),previousEnd.getMonth(), previousEnd.getDate()+1); 123
变成
Date newStart = nextDay(previousEnd); private static Date nextDay(Date arg){ return new Date(arg.getYear(),arg.getMonth(), arg.getDate()+1); } 1234567 动机
外加函数只是权宜之计。如果有可能,仍然应该将这些函数搬移到它们的理想家园。
7.8 Introduce Local Extension(引入本地扩展)
需要为服务类提供一些额外函数,但你无法修改这个类。新建一个类,使它包含额外函数。让这个扩展品成为源类的子类或包装类。
如果需要的额外函数超过2个,外加函数就很难控制它们了。所以,需要将这些函数组织在一起,放到一个恰当的地方去。要达到这一目的,两种标准对象技术——子类化(subclassing)和包装(wrapping)。这种情况下,我们把子类或包装类统称为本地扩展。
使用本地扩展使你得以坚持“函数和数据应该被同一封装”的原则。如果你一直把该放在扩展类中的代码零散地放置于其他类中,最终只会让其他这些类变得过分复杂,并使得其中函数难以被复用。
在子类和包装类之间做选择时,我们通常选择子类,因为这样的工作量比较少。制作子类的最大障碍在于,它必须在对象创建期实施。如果我们可以接管对象创建过程,那当然没问题;但如果你想在你对象创建之后再使用本地扩展。就有问题了。此外,子类化方案还必须产生一个子类对象,这种情况下,如果有其他对象引用了旧对象,我们就同时有两个对象保存了原数据!如果原数据是不可修改的,那也没问题,可以放心复制;但如果原数据是运行被修改,问题就来了,因为一个修改动作无法同时改变两份副本。这时候我们就必须使用包装类。使用包装类时,对本地扩展的修改会波及原对象。
范例
使用JavaDate类为例。在此类上提供我们想要的功能,。
第一件待决定的就是:使用子类还是包装类。
子类显而易见的办法
class MfDateSub extend Date{ public MfDateSub nextDay(){… public int dayOfYear(){... 12345
包装类则需要向上委托
class MfDateWrap{ private Date _original; 123 范例:使用子类
先建立MfDateSub,使其称为Date的子类;
构造函数需要委托给Date构造函数
public class MfDateSub extends Date { public MfDateSub(String dateString) { super(dateString); } public MfDateSub(Date date) { super(date.getTime()); } private static Date nextDay(Date arg){ return new Date(arg.getYear(),arg.getMonth(),arg.getDate()+1); } } 12345678910111213 范例:使用包装类
首先声明一个包装类
public class MfDateWrap { private Date _original; public MfDateWrap(String dateString) { _original = new Date(dateString); } public MfDateWrap (Date arg){ _original=arg; } public int getYear(){ return _original.getYear(); } } 123456789101112131415
网址:重构——在对象之间搬移特性 https://www.yuejiaxmz.com/news/view/510281
相关内容
搬运帮:用心服务,让搬家更轻松自在知识与理想:文学建构对象世界的根据
重构艺术:代码之美
社会:如何在搬家之前,做一个全面的清洗?
在真实与虚构之间的生活审美
移动支付正在构建智慧生活
用印象笔记,搞定搬家这件头疼事。
搬家后怎么清洁移门以及家用电器
搬过家之后 如何对搬家物品进行清洁工作
如何在搬家之前做一个全面的打扫