在对象之间搬移特性
“决定把责任放在哪儿”
搬移函数
一个类有太多的行为,或者如果一个类与另一个类有太多使用而形成的高度耦合,就需要搬移函数。
// 书中的示例
class Account {
private AccountType _type;
private int _daysOverdrawn;
double overdraftCharge() {
if (_type.isPermium()) { // isPermium() 属于 AccountType 的函数
double result = 10;
if (_daysOverdrawn > 7) {
result += (_daysOverdrawn - 7) * 0.85;
}
return result;
} else {
return _daysOverdrawn * 1.75;
}
}
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0) {
result += overdraftCharge();
}
return result;
}
}
// 重构后
class Account {
private AccountType _type;
private int _daysOverdrawn;
int getDaysOverdrawn(){
return _daysOverdrawn;
}
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0) {
result += _type.overdraftCharge(this);
}
return result;
}
}
class AccountType {
double overdaftCharge(Account account) {
if (isPermium()) {
double result = 10;
if (account.getDaysOverdrawn() > 7) {
result += (account.getDaysOverdrawn() - 7) * 0.85;
}
return result;
} else {
return account.getDaysOverdawn() * 1.75;
}
}
}
将函数由一个类迁移到另一个类,在迁移过程中需要注意的问题是所有依赖的方法也必须相应的进行修改。
搬移字段
将一个类的字段由本类迁移到另外一个类中。
// 书中的示例
class Account {
private AccountType _type;
private double _interestRate;
double interestForAmount_days (double amount, int days) {
return _interestRate * amount * days / 365;
}
}
// 重构后
class AccountType {
private double _interestRate;
void setInterestRate(double arg){
_interestRate = arg;
}
double getInterestRate() {
return _interestRate;
}
}
class Account {
private AccountType _type;
double interestForAmount_days (double amount, int days) {
return _type.getInterestRate() * amount * days / 365;
}
}
搬移字段与搬移函数基本类似,只是操作的对象不一样,一个是操作的函数,一个操作的是字段(成员变量)。
提取类
一个类应该是一个清楚的抽象,处理一些明确的责任。
而现状是,在增加一些功能是,基本都放在一个类中,类会越来越大,越来越复杂。而解决这一问题的办法就是将这些本该拆分成多个类的代码提取出来,放入一个新的类中。
// 书中的示例
class person {
private String _name;
private String _officeAreaCode;
private String _officeNumber;
public String getTelephoneNumber() {
return ("(" + _officeAreaCode + ") " + _officeNumber);
}
String getOfficeAreaCode() {
return _officeAreaCode;
}
void setOfficeAreaCode(String arg) {
_officeAreaCode = arg;
}
String getOfficeNumber() {
return _officeNumber;
}
void setOfficeNumber(String arg) {
_officeNumber = arg;
}
}
// 重构后,书中将其提取出了一个 TelephoneNumber 类
class TelephoneNumber {
private String _areacode;
String getAreaCode() {
return _areaCode;
}
void setAreaCode(String arg) {
_areaCode = arg;
}
}
class Person {
public String getTelephoneNumber() {
return ("(" + getOfficeAreaCode() +") " + _officeNumber);
}
Strng getOfficeAreaCode() {
return _officeTelephone.getAreaCode();
}
}
// 将原本属于 Person 类中的 _officeAreaCode 和 _officeNumber 封装到 TelphoneNumber 类中
将类内联化
与 提取类
正好相反,某个类没有做太多事情,将这个类的所有特性搬移到另一个类中,然后移除原类。
// 案例与上一个正好相反,把 TelephoneNumber 类内联到 Person 类中,然后删除 TelephoneNumber 类
思考:某些重构手法正好是两个相反的操作,一个添加,一个移除。如何选择哪个手法才是见功力的,我目前的判断是,看复杂程度,过于复杂就添加,原本有多余的而且就很简单,就删除。
隐藏"委托关系"
“封装”即使不是对象的最关键特征,也是最关键特征之一。
如果某个客户先通过服务对象的字段得到另一个对,然后调用后者的函数,那么客户就必须知晓这一层委托关系。万一委托关系发生变化,客户也得相应进行变化。这时的作法是,可以在服务对象上放置一个简单的委托函数,将委托关系隐藏起来,从而去除这种依赖。
// 书中的示例
class Person {
Department _department;
public Department getDepartment() {
return _department;
}
public void setDepartment(Department arg) {
_department = arg;
}
}
class Department {
private String _chargeCode;
private Person _manager;
public Department(Person manager) {
_manager = manager;
}
public Person getManager() {
return _manager;
}
}
// 调用
manager = john.getDepartment().getManager();
// 重构后
class Department {
private String _chargeCode;
private Person _manager;
public Department(Person manager) {
_manager = manager;
}
public Person getManager() {
return _manager;
}
}
// 调用
manager = john.getManager();
移除中间人
当类提供的函数复杂到一定程度时,我们为了隐藏 ”委托关系“ 而添加的函数越来越多时,索性将 ”委托关系“ 暴露出来,供调用方直接调用。
与 隐藏“委托关系”
正好相反的操作。
// 示例代码与上面相反,去除 Department 类中的 getManager() 方法,直接暴露出 getDepartment() 方法获取中间人,然后调用对应的函数以实现其目的。
引入外加函数
当某个函数需要扩展,而我们无法直接修改这个类时,可以增加函数来实现该功能。
// 书中的示例
Date newStart = new Date(previousEnd.getYear(), previousEnd.getMonth(), previousEnd.getDate() + 1);
// 重构后
private static Date nextDay(Date arg) {
return new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}
我们在项目中开发代码时,经常会写一些工具类(XxxUtils),其实跟这个重构手法是差不多一个操作。
引入本地扩展
与上面的需求基本一致,我们无法直接修改这个类,但需要在这个类的基础上进行一些扩展。此时我们可以有两种 做法:
- 扩展出一个子类
- 增加一个包装类
// 书中的示例
class MfDateSub extends Date {
public MfDateSub nextDay() {
...
}
public int dayOfYear() {
...
}
}
// 包装类
class MfDateWarp {
private Date _original;
public MfDateSub(String dateString) {
_original = new Date(dateString);
}
public MfDateWarp(Date date) {
_original = date;
}
}
参考资料:
[1]: https://book.douban.com/subject/4262627/ "重构-改善既有代码的设计"