在对象之间搬移特性

把字段或函数放在正确的位置或合适的对象中。

1 搬移函数 (Move Method)

使用场景:一个函数与另一个类有更多的交流,或该函数与另一个类高耦合。

重构步骤:

  1. 确定搬移范围:源函数使用的字段和函数。 如果这些字段与函数只被源函数使用,则将其一起搬移;
    如果还有其他函数使用这些字段或函数,考虑将这些函数也一并搬移。
  2. 如果源类的子类与父类有源函数的其他声明,则不能搬移。
  3. 在目标类声明这个函数,并将源函数代码复制到目标函数中。
    如果目标函数可能有源对象的引用,要决定如何引用源对象(新建字段或参数传递)。
    如果源函数有异常处理,要决定把异常处理放在合适的位置(源类或目标函数)。
  4. 决定源类中如何引用目标对象(新建字段或参数传递)。
  5. 修改源函数使其成为一个委托函数。
  6. 移除源函数,并将源函数的所有引用点替换为对目标函数的调用。

注意:如果源函数比较复杂,可以先使用Extract Method重构源函数,然后把提取的函数进行函数搬移。

例子:
// 重构前代码

public class Company {

    // 公司状态-新增
    public static final String STATUS_NEW = "0";
    // 公司状态-已修改
    public static final String STATUS_REVISED = "1";
    // 公司状态-通过审核
    public static final String STATUS_APPROVED = "3";

    private String name;

    private String status;

    // Company Other Fields

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    // other field's get/set method

}

/**
 * 服务类
 * @param company
 */
public class CompanyServiceImpl {

    /**
     * 审核公司信息
     * @param company
     */
    public void checkCompanyInfo(Company company) {
        if (isComNeedCheck(company)) {
            // check company information
        }

        // do other thing...
    }

    // 下面三个方法都依赖的是Company类,将其放到Company类中更为合适

    private boolean isComNeedCheck(Company company) {
        return isComStatusNew(company) || isComRevised(company);
    }

    private boolean isComRevised(Company company) {
        return Company.STATUS_REVISED.equals(company.getStatus());
    }

    private boolean isComStatusNew(Company company) {
        return Company.STATUS_NEW.equals(company.getStatus());
    }

}

显然可以看出服务类中的方法isComNeedCheck、isComRevised、isComStatusNew都只和Company类进行交流,所以将其放在Company类中更为合适。
// 重构后代码

public class Company {

    // 公司状态-新增
    public static final String STATUS_NEW = "0";
    // 公司状态-已修改
    public static final String STATUS_REVISED = "1";
    // 公司状态-通过审核
    public static final String STATUS_APPROVED = "3";

    private String name;

    private String status;

    // Company Other Fields

    public boolean isComNeedCheck() {
        return isComStatusNew() || isComRevised();
    }

    public boolean isComRevised() {
        return Company.STATUS_REVISED.equals(getStatus());
    }

    public boolean isComStatusNew() {
        return Company.STATUS_NEW.equals(getStatus());
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    // other field's get/set method

}

public class CompanyServiceImpl {

    /**
     * 审核公司信息
     * @param company
     */
    public void checkCompanyInfo(Company company) {
        if (company.isComNeedCheck()) {
            // check company information
        }

        // do other thing...
    }

}

重构后代码结构更加清晰,便于后期根据模块来进行维护。

2 搬移字段(Move Field)

使用场景:某个字段被另一个类更多的使用。

重构方法:在目标类新建一个字段,修改源字段的所有用户,令它们调用新字段。

重构步骤:

  1. 封装要搬移的字段。
  2. 在目标类中新建一个相同的字段,并建立相应的设置取值函数。
  3. 决定在源对象中如何引用目标对象。
  4. 删除源字段,并将对源字段的所有相关调用替换为对目标函数的调用(设置、取值函数)。

例子:
// 重构前代码

public class Account {

    // 利率
    private double intersetRate;

    /**
     * 计算利率
     * @param amount
     * @param days
     * @return
     */
    public double interestForAmountAndDays(double amount, int days) {
        return this.intersetRate * amount * days / 365;
    }

}

public class AccountType {

}

利率与账户并没有什么关系,所有intersetRate放在Account类中并不合适。我们假设利率与账户类型AccountType有关系。
// 重构后代码

public class Account {

    private AccountType type;

    public double interestForAmountAndDays(double amount, int days) {
        return type.getIntersetRate() * amount * days / 365;
    }

}

public class AccountType {

    private double intersetRate;

    public double getIntersetRate() {
        return intersetRate;
    }

    public void setIntersetRate(double intersetRate) {
        this.intersetRate = intersetRate;
    }

}

3 提炼类(Extract Class)

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

使用场景:随着业务的增加,在某个类中添加了过多的数据与函数,使该类的职责不清晰。

重构方法:建立一个新类,将相关的字段和函数搬移至新类。

重构步骤:

  1. 确定如何分解类的责任。
  2. 建立一个新类,用于表现从源类中分离出来的责任。
  3. 建立从源类访问新类的连接关系。
  4. 把必要的字段与函数搬移至新类。
  5. 每次搬移后,编译测试;
  6. 精简每个类的接口。如果你建立起双向连接,检查是否可以将其改为单向连接。
  7. 决定是否公开新类。如果要公开,决定使其成为引用对象或不可变对象。

例子:
// 重构前代码

public class Person {

    private String name;
    /**
     * 区号
     */
    private String areaCode;
    /**
     * 电话号码
     */
    private String telNumber;

    /**
     * 获取电话号码
     * @return
     */
    public String getTelephoneNumber() {
        return getAreaCode() + "-" + getTelNumber();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAreaCode() {
        return areaCode;
    }

    public void setAreaCode(String areaCode) {
        this.areaCode = areaCode;
    }

    public String getTelNumber() {
        return telNumber;
    }

    public void setTelNumber(String telNumber) {
        this.telNumber = telNumber;
    }

}

Person类中的字段areaCode、telNumber,方法getTelephoneNumber()都是与电话相关,可以考虑将其提取到一个新类中,以便把与电话相关的代码都放在一个类中,比如后面又添加了校验电话号码的代码等等,便于以后维护。
// 重构后代码

public class Person {

    private String name;

    private TelephoneNumber telNum;

    public String getTelephoneNumber() {
        return telNum.getTelephoneNumber();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

/**
 * 抽取的新类
 */
public class TelephoneNumber {

    private String areaCode;

    private String number;

    public String getTelephoneNumber() {
        return getAreaCode() + "-" + getNumber();
    }

    public boolean checkTelephoneNumber() {
        // check ....
        return true;
    }

    public String getAreaCode() {
        return areaCode;
    }

    public void setAreaCode(String areaCode) {
        this.areaCode = areaCode;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

}

4 将类内联化(Inline Class)

内联化类是与提炼类相反的重构手法。

使用场景:如果一个类没有做太多事情,或不再承担足够的责任,就将其内联化到另一个类中。

重构方法:将这个类的所有特性搬移到另一个类中,然后删除原类。

重构步骤:

  1. 在目标类中声明源类的所有public字段和函数,并将其中的所有函数委托至源类。
  2. 把源类的所有引用点替换为对目标类的调用。
  3. 编译测试。
  4. 运用Move Field和Move Method,把源类的所有特性搬移至目标类。
  5. 删除源类。

例子:
假如7.3中重构后的类TelephoneNumber 就是那么的简单,确认没有独立存在的必要的话,可以使用内联化重构手法将TelephoneNumber 的所有字段和方法搬移至Person类中,然后将TelephoneNumber 移除。
// 重构前代码
见7.3中重构后代码。
// 重构后代码
见7.3中重构前代码。

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

使用场景:客户先获取服务类中的委托对象,然后再调用委托类的函数,暴露了委托关系。

重构方法:在服务类中建立客户所需的所有函数,用于隐藏委托关系。

重构步骤:

  1. 对于每一个委托关系中的函数,在服务对象中新建一个简单的委托函数。
  2. 调整客户代码,使其只调用服务对象提供的函数。
  3. 每次调整后,编译测试。
  4. 如果没有任何客户调用受托类,移除服务对象中相关的访问函数。
  5. 编译测试。

例子:
// 重构前代码

public class Person {

    private String name;

    private TelephoneNumber telNum;

    public Person(String name, String areaCode, String number) {
        this.name = name;
        telNum = new TelephoneNumber(areaCode, number);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public TelephoneNumber getTelNum() {
        return telNum;
    }

    public void setTelNum(TelephoneNumber telNum) {
        this.telNum = telNum;
    }

}

public class TelephoneNumber {

    private String areaCode;

    private String number;

    public TelephoneNumber(String areaCode, String number) {
        super();
        this.areaCode = areaCode;
        this.number = number;
    }

    public String getTelephoneNumber() {
        return getAreaCode() + "-" + getNumber();
    }

    public String getAreaCode() {
        return areaCode;
    }

    public void setAreaCode(String areaCode) {
        this.areaCode = areaCode;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

}

public class Test {

    public static void main(String[] args) {
        Person person = new Person("liuz", "025", "62526001");
        // 显式调用委托对象的方法
        String telephoneNum = person.getTelNum().getTelephoneNumber();
        System.out.println(telephoneNum);
    }

}

// 重构后代码

public class Person {

    private String name;

    private TelephoneNumber telNum;

    public Person(String name, String areaCode, String number) {
        this.name = name;
        telNum = new TelephoneNumber(areaCode, number);
    }

    public String getTelephoneNumber() {
        return telNum.getTelephoneNumber();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

public class Test {

    public static void main(String[] args) {
        Person person = new Person("liuz", "025", "62526001");
        // 隐藏委托关系
        String telephoneNum = person.getTelephoneNumber();
        System.out.println(telephoneNum);
    }

}

6 移除中间人(Remove Middle Man)

使用场景:服务类中有过多的委托函数时,可以通过移除简单委托函数来简化服务类代码。

重构方法:让客户直接调用受托类。

重构步骤:

  1. 建立一个新函数,用以获得受托对象。
  2. 删除服务类中的简单委托函数,并将其所有引用点替换为调用受托对象。
  3. 处理每个委托函数后,编译测试。
    注意:可以根据需要,设置获取受托对象函数的可见范围,并保留部分委托函数。

例子:
该重构手法与隐藏委托关系重构手法相反,例子可以把7.5中的例子反过来即可。

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

使用场景:你需要为服务类新增一个函数,但你却无法修改这个类。

重构方法:在客户类新建一个函数,并把服务类实例以第一参数传入这个新建的函数。

重构步骤:

  1. 在客户类新建一个函数,用于提供你需要的功能。
    由于这个函数本应该在服务类中,为了以后搬移函数方便,新建的函数不能调用客户类的任何特性。
    如果需要客户类的某个特性的话,就以参数形式传递给该函数。
  2. 以服务类实例作为该函数的第一个参数。
  3. 为了与客户类函数进行区分,将该函数注释为:外加函数,应在服务类实现。

例子:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class IntroduceForeignMethod {

    public static void main(String[] args) throws ParseException {

        Calendar cal = Calendar.getInstance();
        System.out.println("当前时间:\n" + cal.getTime());

        //
        System.out.println("执行Calendar类的外加函数nextDate");
        IntroduceForeignMethod fm = new IntroduceForeignMethod();
        Date nextDate = fm.nextDate(Calendar.getInstance());
        System.out.println(nextDate);

        //
        System.out.println("执行Calendar类的外加函数truncateDate");
        System.out.println(fm.truncateDate(cal));

        //
        System.out.println("执行Date类的外加函数truncate");
        Date trunDate = fm.truncate(cal.getTime(), "yyyy/MM/dd");
        System.out.println(trunDate);
    }

    /**
     * 获取下一天的日期<br/>
     * 外加函数, 应在Calendar类中
     * @param cal
     * @return
     */
    public Date nextDate(Calendar cal) {
        cal.add(Calendar.DAY_OF_YEAR, 1);
        Date nextDate = cal.getTime();
        return nextDate;
    }

    /**
     * 截取日期,只保留年月日<br/>
     * 外加函数, 应在Calendar类中
     * @param cal
     * @return
     */
    public Date truncateDate(Calendar cal) {
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        return cal.getTime();
    }

    /**
     * 根据指定格式截取日期<br/>
     * 外加函数, 应在Date类中
     * @param date
     * @param format
     * @return
     */
    public Date truncate(Date date, String format) throws ParseException {
        if (date == null || format == null) {
            return date;
        }
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        return sdf.parse(sdf.format(date));
    }

}

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

使用场景:你需要为服务类新增一些额外的函数,但你却无法修改这个类。

重构方法:新建一个类,使它包含这些额外函数。让这个新类成为服务类的子类或包装类(统称为本地扩展)。

重构步骤:

  1. 新建一个扩展类,将它作为原始类的子类或包装类。
  2. 在扩展类中加入转型构造函数。
    转型构造函数:接受原对象作为参数的构造函数。
    如果采用子类化方案,转型构造函数应该调用适当的超类构造函数;
    如果采用包装类方案,转型构造函数应该将它得到的传入参数以实例变量的形式保存起来,用作接受委托的原对象。
  3. 在扩展类中加入新特性。
  4. 根据需要,将原对象替换为扩展对象。
  5. 将针对原始类定义的所有外加函数搬移到扩展类中。

例子:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class ConcreateCalendar extends Calendar {

    private static final long serialVersionUID = -4133479289114370006L;

    private Date today;

    public ConcreateCalendar(String dateStr, String format) {
        super();
        try {
            today = new SimpleDateFormat(format).parse(dateStr);
        } catch (ParseException e) {
            today = new Date();
        }
    }

    public Date getToday() {
        return today;
    }

    public void setToday(Date today) {
        this.today = today;
    }

    protected void computeTime() {
        // TODO Auto-generated method stub

    }

    protected void computeFields() {
        // TODO Auto-generated method stub

    }

    public void add(int field, int amount) {
        // TODO Auto-generated method stub

    }

    public void roll(int field, boolean up) {
        // TODO Auto-generated method stub

    }

    public int getMinimum(int field) {
        // TODO Auto-generated method stub
        return 0;
    }

    public int getMaximum(int field) {
        // TODO Auto-generated method stub
        return 0;
    }

    public int getGreatestMinimum(int field) {
        // TODO Auto-generated method stub
        return 0;
    }

    public int getLeastMaximum(int field) {
        // TODO Auto-generated method stub
        return 0;
    }

}

import java.util.Calendar;
import java.util.Date;
/**
 * 子类化方案
 *
 */
public class LocalExtensionBySubClass extends ConcreateCalendar {

    private static final long serialVersionUID = -3645662145076608166L;

    /**
     * 转型构造函数
     * @param dateStr
     * @param format
     */
    public LocalExtensionBySubClass(String dateStr, String format) {
        super(dateStr, format);
    }

    /**
     * 下一个日期
     * @return
     */
    public Date nextDate() {
        setTime(this.getToday());
        add(Calendar.DAY_OF_YEAR, 1);
        return getTime();
    }

    /**
     * 下一个月份
     * @return
     */
    public Date nextMonth() {
        setTime(this.getToday());
        add(Calendar.MONTH, 1);
        return getTime();
    }

    /**
     * 下一年
     * @return
     */
    public Date nextYear() {
        setTime(this.getToday());
        add(Calendar.YEAR, 1);
        return getTime();
    }

}

import java.util.Calendar;
import java.util.Date;
/**
 * 包装类方案
 *
 */
public class ConcreateCalendarWrap {

    private Calendar cal;

    /**
     * 转型构造函数
     * @param cal
     */
    public ConcreateCalendarWrap(Calendar cal) {
        super();
        this.cal = cal;
    }

    /**
     * 下一个日期
     * @return
     */
    public Date nextDate() {
        cal.add(Calendar.DAY_OF_YEAR, 1);
        return cal.getTime();
    }

    /**
     * 下一个月份
     * @return
     */
    public Date nextMonth() {
        cal.add(Calendar.MONTH, 1);
        return cal.getTime();
    }

    /**
     * 下一年
     * @return
     */
    public Date nextYear() {
        cal.add(Calendar.YEAR, 1);
        return cal.getTime();
    }

    public Date getTime() {
        return cal.getTime();
    }

    public void add(int field, int amount) {
        cal.add(field, amount);
    }

    // ......

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值