子债父还 消除重复代码的重构技巧-《代码不朽》

大家好,我是码农老吴,欢迎收看架构师书房。今天,我继续给大家解读《代码不朽》这本书。

上期,我们聊完了如何通过提取静态方法,消除重复代码。本期,我们聊聊消除重复代码的第二个方法,提取父类,也就是通过面对对象里面的继承,来消除重复代码。儿子欠了技术债,父亲来还债,子债父还吗。还是上次的案例。

子债父还 消除重复代码的重构技巧-《代码不朽》

重构之前

类图

子债父还 消除重复代码的重构技巧-《代码不朽》

接口及类

CheckingAccount:支票账户

SavingsAccount:储蓄账户

Accounts:账户工具类

Transfer:转账类

Accounts:账户工具类

package eu.sig.training.ch04.v2;

public class Accounts {
    @SuppressWarnings("unused")
    public static CheckingAccount findAcctByNumber(String number) {
        return new CheckingAccount();
    }

    // tag::isValid[]
    public static boolean isValid(String number) {
        int sum = 0;
        for (int i = 0; i < number.length(); i++) {
            sum = sum + (9 - i) * Character.getNumericValue(number.charAt(i));
        }
        return sum % 11 == 0;
    }
    // end::isValid[]
}

CheckingAccount:支票账户

package eu.sig.training.ch04.v2;

import eu.sig.training.ch04.BusinessException;
import eu.sig.training.ch04.Money;

// tag::CheckingAccount[]
public class CheckingAccount {
    private int transferLimit = 100;

    public Transfer makeTransfer(String counterAccount, Money amount)
        throws BusinessException {
        // 1. Check withdrawal limit:
        if (amount.greaterThan(this.transferLimit)) {
            throw new BusinessException("Limit exceeded!");
        }
        if (Accounts.isValid(counterAccount)) { // <1>
            // 2. Look up counter account and make transfer object:
            CheckingAccount acct = Accounts.findAcctByNumber(counterAccount);
            Transfer result = new Transfer(this, acct, amount); // <2>
            return result;
        } else {
            throw new BusinessException("Invalid account number!");
        }
    }

}
// end::CheckingAccount[]

SavingsAccount:储蓄账户

package eu.sig.training.ch04.v2;

import eu.sig.training.ch04.BusinessException;
import eu.sig.training.ch04.Money;

// tag::SavingsAccount[]
public class SavingsAccount {
    CheckingAccount registeredCounterAccount;

    public Transfer makeTransfer(String counterAccount, Money amount) 
        throws BusinessException {
        // 1. Assuming result is 9-digit bank account number,
        // validate with 11-test:
        if (Accounts.isValid(counterAccount)) { // <1>
            // 2. Look up counter account and make transfer object:
            CheckingAccount acct = Accounts.findAcctByNumber(counterAccount);
            Transfer result = new Transfer(this, acct, amount); // <2>
            if (result.getCounterAccount().equals(this.registeredCounterAccount)) 
            {
                return result;
            } else {
                throw new BusinessException("Counter-account not registered!");
            }
        } else {
            throw new BusinessException("Invalid account number!!");
        }
    }

}
// end::SavingsAccount[]

Transfer:转账类

package eu.sig.training.ch04.v2;

import eu.sig.training.ch04.Money;

public class Transfer {
    CheckingAccount counterAccount;

    @SuppressWarnings("unused")
    public Transfer(CheckingAccount acct1, CheckingAccount acct2, Money m) {}

    @SuppressWarnings("unused")
    public Transfer(SavingsAccount acct1, CheckingAccount acct2, Money m) {}

    public CheckingAccount getCounterAccount() {
        return this.counterAccount;
    }

}

头脑风暴

CheckingAccount:支票账户和SavingsAccount:储蓄账户,从这两个类的名字上,就可以看出,他们的相似性,从面向对象的角度看,这两个类,应该都属于账户。就可以建立一个新的类,账户类,然后,将两个子类中,重复的代码,提取到父类中,基于继承实现代码复用。

重构之后

类图

子债父还 消除重复代码的重构技巧-《代码不朽》

接口及类

Account:账户父类

CheckingAccount:支票账户

SavingsAccount:储蓄账户

Account:账户父类

父类的makeTransfer方法,提供了一种默认的转账操作,子类中,如果该方法,没有变化,则可以直接复用,反之,覆盖父类的方法即可。

package eu.sig.training.ch04.v3;

import eu.sig.training.ch04.BusinessException;
import eu.sig.training.ch04.Money;

// tag::Account[]
public class Account {
    public Transfer makeTransfer(String counterAccount, Money amount)
        throws BusinessException {
        // 1. Assuming result is 9-digit bank account number, validate 11-test:
        int sum = 0; // <1>
        for (int i = 0; i < counterAccount.length(); i++) {
            sum = sum + (9 - i) * Character.
                getNumericValue(counterAccount.charAt(i));
        }
        if (sum % 11 == 0) {
            // 2. Look up counter account and make transfer object:
            CheckingAccount acct = Accounts.findAcctByNumber(counterAccount);
            Transfer result = new Transfer(this, acct, amount); // <2>
            return result;
        } else {
            throw new BusinessException("Invalid account number!");
        }
    }
}
// end::Account[]

CheckingAccount:支票账户

子类makeTransfer方法,既有自己的业务逻辑,又通过super关键字调用了父类提供的makeTransfer();

package eu.sig.training.ch04.v3;

import eu.sig.training.ch04.BusinessException;
import eu.sig.training.ch04.Money;

// tag::CheckingAccount[]
public class CheckingAccount extends Account {
    private int transferLimit = 100;

    @Override
    public Transfer makeTransfer(String counterAccount, Money amount)
        throws BusinessException {
        if (amount.greaterThan(this.transferLimit)) {
            throw new BusinessException("Limit exceeded!");
        }
        return super.makeTransfer(counterAccount, amount);
    }
}
// end::CheckingAccount[]

SavingsAccount:储蓄账户

同上

package eu.sig.training.ch04.v3;

import eu.sig.training.ch04.BusinessException;
import eu.sig.training.ch04.Money;

// tag::SavingsAccount[]
public class SavingsAccount extends Account {
    CheckingAccount registeredCounterAccount;

    @Override
    public Transfer makeTransfer(String counterAccount, Money amount)
        throws BusinessException {
        Transfer result = super.makeTransfer(counterAccount, amount);
        if (result.getCounterAccount().equals(this.registeredCounterAccount)) {
            return result;
        } else {
            throw new BusinessException("Counter-account not registered!");
        }
    }
}
// end::SavingsAccount[]

点评

提取父类,作为一种重构技巧,无可厚非。但是,根据我多年的工作经验,从多个相似的类中,提取它们的父类,发生的概率一般不高,原因如下。

1,先建立接口和父类,然后增加子类:这是比较常见的情况,不然要架构师干啥呢,连系统中哪里需要建立父类,都识别不出来,能力堪忧啊。

2,先有一个类(将来会进化为子类),然后在系统扩展时,增加第二个相似的类之前,提取父类,否则说明程序员对项目一点都不了解。也就是意味着,在增加第二个子类之前,父类就应该设计出来。

关于第三个原则,不写重复代码(Write Code Once),通过提取静态方法和提取父类,消除重复代码,我们就聊到这里,下期,我们开始聊第四个原则,保持代码单元的接口简单(Keep Unit Interfaces Small)。

极客架构师,专注架构师成长,我们下期见。

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章