重构-改善既有代码的设计(七):在对象之间搬移特性

    xiaoxiao2023-10-22  180

    1、搬移函数(Move Method)

    (1)症状:如果一个类有太多行为,或一个类与另一个类有太多合作而形成高度耦和

    (2)解决:在该函数最常引用的类中建立一个有着类似行为的新函数,将旧函数变成一个单纯的委托函数,或者是将旧函数完全移除

    (3)检查:调用端、被调用端、继承体系中任何一个重定义函数

    (4)如果源函数包含异常,需要判断逻辑上应该由哪个类来处理这一异常,如果应该由源类来负责,就把异常留在原地

    (5)可以删除源函数,或将它当作一个委托函数保留下来

    (6)如果要移除源函数,请将源类中对源函数的所有调用都替换成对目标函数的调用

    //某银行系统中包含一个银行账户类BankAccount和账户利息类AccountInterest class BankAccount { private int accountAge; private int creditScore; private AccountInterest accountInterest; public BankAccount(int accountAge, int creditScore, AccountInterest accountInterest) { this.accountAge = accountAge; this.creditScore = creditScore; this.accountInterest = accountInterest; } public int getAccountAge() { return this.accountAge; } public int getCreditScore() { return this.creditScore; } public AccountInterest getAccountInterest() { return this.accountInterest; } public double calculateInterestRate() { if (this.creditScore > 800) { return 0.02; } if (this.accountAge > 10) { return 0.03; } return 0.05; } } class AccountInterest { private BankAccount account; public AccountInterest(BankAccount account) { this.account = account; } public BankAccount getAccount() { return this.account; } public double getInterestRate() { return account.calculateInterestRate(); } public boolean isIntroductoryRate() { return (account.calculateInterestRate() < 0.05); } } 说明:很明显AccountInterest使用calculateInterestRate()方法更为频繁,它更希望得到该方法 ---------------------------------------------------------------------------- 重构(搬移函数) 将calculateInterestRate()方法从BankAccount类搬移到AccountInterest类中 class BankAccount { private int accountAge; private int creditScore; private AccountInterest accountInterest; public BankAccount(int accountAge, int creditScore, AccountInterest accountInterest) { this.accountAge = accountAge; this.creditScore = creditScore; this.accountInterest = accountInterest; } public int getAccountAge() { return this.accountAge; } public int getCreditScore() { return this.creditScore; } public AccountInterest getAccountInterest() { return this.accountInterest; } } class AccountInterest { private BankAccount account; public AccountInterest(BankAccount account) { this.account = account; } public BankAccount getAccount() { return this.account; } public double getInterestRate() { return calculateInterestRate(); } public boolean isIntroductoryRate() { return (calculateInterestRate() < 0.05); } //将calculateInterestRate()方法从BankAccount类搬移到AccountInterest类 public double calculateInterestRate() { if (account.getCreditScore() > 800) { return 0.02; } if (account.getAccountAge() > 10) { return 0.03; } return 0.05; } }

    总结:通过重构,BankAccount类更加符合单一职责原则,它负责存储银行账户信息,而对账户的操作(例如计算利息等)方法则转移到其他经常使用且适合它的类中,这样让代码变得更加合理,也有助降低类之间的耦合度,增强代码的可扩展性和可维护性。

     

    2、搬移字段(Move Filed)

    (1)症状:某个字段被其所驻类之外的另一个类中更多地用到

    (2)解决:在目标类新建一个字段,修改源字段的所有用户,令它们改用新字段

    (3)如果需要读取该变量,就把对源字段的引用替换成对目标取值函数的调用

    (4)如果要对该变量赋值,就把对源字段的引用替换成对设值函数的调用

    (5)如果源字段不是private的,就必须在源类的所有子类中查找源字段的引用点,并进行相应的替换

    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; }

    (6)如果字段在当前类已经被多个函数使用了,那就应该使用自我封装

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

    3、提炼类(Extract Class)

    (1)症状:某个类做了应该由两个类做的事

    (2)解决:建立一个新类,将相关的字段和函数从旧类搬移到新类

    (3)一个类应该是一个清楚的抽象,处理一些明确的责任

    (4)如果旧类剩下的责任与旧类名称不符,为旧类更名

    (5)使用Move Method 将必要函数搬移到新类,先搬移较低层次的函数(也就是“被其他函数调用”多于“调用其他函数”),再搬移较高层次函数

    (6)如果你建立起双向链接,检查是否可以将它改为单向链接,在真正需要之前,不要建立“从新类通往旧类”的链接

    class Person.. private String _name; private String _officeAreaCode; private String _officeNumber; public String getName() public String getOfficeAreaCode() public String getOfficeNumber() public String getTelephoneNumber() -------------------------------------------------------------------- 重构(提炼类),如下 class Person.. private String _name; private TelephoneNumber _officeTelephone; //建立从Person到TelephoneNumber的链接 private TelephoneNumber _officeTelephone = new TelephoneNumber(); public String getTelephoneNumber(){ return _officeTelephone.getTelephoneNumber(); } class TelephoneNumber.. private String _areaCode; public String getAreaCode() public void setAreaCode(String _areaCode) public String getTelephoneNumber()

    (7) Extract Class 是改善并发程序的一种常用技术,它使得你可以为提炼后的两个类分别加锁,当然也面临一定危险,如果需要确保两个对象被同时锁定,就需要处理事务问题

     

    4、将类内联化(Inline Class)

    (1)症状:某个类没有做太多事情

    (2)解决:将这个类的所有特性搬移到另一个类中,移除原类

    (3)注意捕捉原类的隐藏引用点

    将原类声明为private,以斩断包之外的所有引用可能修改原类的名称,可使编译器帮助你捕捉到所有对于原类的隐藏引用点修改所有原类引用点,改而引用目标类

    5、隐藏“委托关系”(Hide Delegate)

    (1)症状:客户直接调用其server object(服务对象)的delegate class

    (2)解决:在server端(某个class)建立客户所需的所有函数,用以隐藏委托关系。

    (3)封装意味着每个对象都应该尽可能少了解系统的其他部分,一旦发生变化,需要了解这一变化的对象就会比较少

    (4)通过委托函数可以把委托关系隐藏起来,即便将来委托关系上发生变化,变化也会被限制在服务对象中,不会波及客户

    (5)隐藏委托关系的做法

    对于每一个委托关系中的函数,在服务对象端建立一个简单的委托函数调整客户,令它只调用服务对象提供的函数,若使用者和服务者不在同一个包里,考虑修改委托函数的访问权限,让客户得以在包外调用每次调整后,编译如果将来不再有任何客户需要访问委托函数,可移除服务对象中的相关访问函数

    代表[人]的Person和代表[部门]的Department: 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; } ... 说明:如果客户希望知道某人的经理是谁,他必须先取得Department对象: manager = john.getDepartment().getManager(); 这样就对客户揭露了Department工作原理,需要对客户隐藏Department,减少耦合(coupling) ------------------------------------------------------------------------------- 重构(隐藏“委托关系”) 1、在Person中建立一个简单的委托函数 public Person getManager() { return _department.getManager(); } 2、修改Person的所有客户,让其改用新函数 manager = john.getManager();

    6、移除中间人(Remove Middle Man)

    (1)症状:如果某个类做了过多的简单委托动作

    (2)解决:让客户直接调用受委托类

    (3)隐藏“委托关系”的代价:每当客户要使用受委托类的新特性时,就必须在服务端添加一个简单的委托函数

    (4)如果受委托类的特性(功能)越来越多,服务类可能完全变成一个“中间人”,“中间人”类除了调用别的对象之外不做任何事情,所以“中间人”类没有存在的必要,我们可以将它们从代码中删除,从而让交互的两个类直接关联

    (5)把 在中间关联而不起任何其他作用的类移除,让有关系的两个类直接进行交互

    class Person... Department _department; public Person getManager() { return _department.getManager(); class Department... private Person _manager; public Department (Person manager) { _manager = manager; } 为了找出某人的经理,客户代码可能这样写: manager = john.getManager(); -------------------------------------------------------------------------- 重构(移除中间人) class Person... public Department getDepartment() { return _department; } 首先获得受托对象(delegate),然后直接使用之: manager = john.getDepartment().getManager(); 可以删除Person的getManager() 函数

    7、引入外加函数(Introduce Foreign Method)

    (1)症状:需要为提供服务的类增加一个函数,但你无法修改这个类

    (2)解决:在客户类中建立一个函数,并以第一参数形式传入一个服务类实例

    Date newStart = new Date(previousEnd.getYear(), previousEnd.getMonth(), previousEnd.getDate() + 1); --------------------------------------------------------------- Date newStart = nextDay(previousEnd); private static Date nextDay(Date arg){ return new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1) }

    (3)如果发现自己为一个服务类建立了大量外加函数,或发现有许多类都需要同样的外加函数,就不应该再使用本项重构

    (4)外加函数只是权宜之计,应该添加注释“外加函数,应该在服务类中实现”

     

    8、引入本地扩展(Introduce Local Extension)

    (1)症状:你需要为服务类提供一些额外函数,但你无法修改这个类

    (2)解决:建立一个新类,使它包含这些额外函数,让这个新类成为源类的子类或者包装类

    (3)在无法修改源码的情况下,若只添加一两个函数,可以使用外加函数实现,若额外函数超过两个,外加函数一般就很难控制了,此时就采用子类化和包装实现本地扩展

    (4)使用包装类时,对本地扩展的修改会波及原对象,反之亦然

    建立一个扩展类,作为原始类的子类或者包转类在扩展类中加入转型构造函数:是指接受原对象作为参数的构造函数在扩展类中加入新特性根据需要,将原对象替换为扩展对象将针对原始类定义的所有外加函数搬移到扩展类中

     

    尾注

    上述的总结与思考是基于对《重构—改善既有代码的设计》这本书的精读与演绎更多及时干货,请关注微信公众号:JAVA万维猿圈

    最新回复(0)