重构(Martin Fowler)——处理概括关系

有一批重构手法专门用来处理类的概括关系(继承关系),其中主要是将函数上下移动于继承体系中

Pull Up Field Pull Up Method 都用于将特性向继承体系的上端移动

Push Down Method Push Down Field 则将特性向继承体系的下端移动

构造函数比较难以向上拉动,因此专门有一个Pull Up Constructor Boby处理它

我们不会将构造函数往下推,因为Replace Constructor with Factory Method通常更管用

如果若干函数大体相似,只是细节上有所差异,可以使用Form Template Method将它们的共同点和不同点分开

除了在继承体系中移动特性之外,你还可以建立新类,改变整个继承体系。Extract SubclassExtract SuperclassExtract Interface都是这样的重构手法,它们在继承体系的不同位置构造出新元素。如果你想在类型系统中标示一小部分函数,Extract Interface特别有用。

如果你发现继承体系中的某些没有存在必要,可以使用Collapse Hierarchy将它们移除

有时候你会发现继承并非最佳选择,你真正需要的其实是委托,可以使用Replace Inheritance with Delegation可以帮助你把继承改为委托。

 

目录

1.1 Pull Up Field(字段上移)

动机

1.2Pull Up Method(函数上移)

动机

做法

1.3Pull Up Constructor Body(构造函数本题上移)

1.4Push Down Method(函数下移)

1.5Push Down Field(字段下移)

1.6Extract SUbclass(提炼子类)

动机

做法

1.7Extract Superclass(提炼超类)

动机

做法

1.8Extract Interface(提炼接口)

1.9Collapse Hierarchy(折叠继承体系)

1.10Form Template Method(塑造模板函数)

1.11Replace Inheritance with Delegation(以委托取代继承)

动机

做法

1.12Replace Delegation with Inheritance(以继承取代委托)


 

1.1 Pull Up Field(字段上移)

两个子类拥有相同的字段

将该字段移动到父类

动机

如果各个子类是分别开发的,或者是在重构过程中组合起来的,那么你会发现他们之间有很多的重复特性,那么此时就需要将这些重复特性移动至父类中。

 

1.2Pull Up Method(函数上移)

有些函数,在各个子类中缠身完全相同的结果

将该函数移至父类

动机

避免行为重复是很重要的,毕竟重复本身就是错误的孳生地,此外别无价值。无论如何,只要系统之内程序出现重复,那么你就会面临“修改其中一个却未修改另一个”的风险。

如果某个函数在各子类中的函数体都相同,那么显然是使用函数上移的好地方。

Pull Up Method过程中最麻烦的一点就是:被提升的函数可能会引用只出现于子类而不出现于超类的特性。

如果被引用的是个函数,你可以将该函数一起提升到父类中,或者在超类中建立一个抽象函数。在此过程中,你可能需要修改某个函数的签名,或建立一个委托函数。

做法

Customer表示顾客,它有两个子类,表示“普通顾客”的Regular Customer 和 表示“贵宾”的PreFerred Customer

两个子类都有一个 createBill() 函数,而且代码完全一样:

void createBill(){
    double chargeAmount = chargeFor(lastBillDate,date);
    addBill(date,charge);
}

但我不能直接把这个函数上移到超类,因为各个子类的 chargeFor() 函数并不相同。我必须先在超类中声明 chargeFor() 抽象函数

class Customer{
public:
    virtual chargeFor() = 0;
};

然后我们将 createBill() 函数从其中一个子类复制到超类。

 

1.3Pull Up Constructor Body(构造函数本题上移)

你在各个子类中拥有一些构造函数,它们的本体几乎完全一致。

在超类中新建一个构造函数,并在子类构造函数中调用它

class Manger : Employee{
public:
    Manger(QString name,QString id,int grade);
private:
    QString _name;
    QString _id;
    int _grade;
};
Manger::Manger(QString name,QString id,int grade){
    this->_name = name;
    this->_id = id;
    this->_grade = grade;
}

重构为:

class Employee{
public:
    Employee(QString name,QString id);
};
Manger::Manger(QString name,QString id,int grade):Employee(name,id){
    this->_grade = grade;
}

当你看见各个子类中的函数有共同行为,第一个念头应该是将共同行为提炼到一个独立函数中,然后将这个函数提升到父类。

对构造函数来说,他们彼此的共同行为往往就是“对象的建构”。这时候你需要在父类中提供一个构造函数,然后让子类来调用。

1.4Push Down Method(函数下移)

父类中额某个函数只与部分(而非全部)子类有关

将这个函数移到相关的那些子类去

重构为:

Push Down Method Pull Up Method 恰恰相反。当我有必要把某些行为从父类移动至子类时

我就会使用Push Down Method,他们通常只在这种时候有用。使用 Extract Subclass 之后你可能会需要它。

1.5Push Down Field(字段下移)

父类中的某个字段只被部分(而非全部)子类用到

将这个字段移到需要它的那些子类去

重构为:

 

1.6Extract SUbclass(提炼子类)

类中的某些特性只被某些(而非全部)实例用到

新建一个子类,将上面所说的那一部分特性转移到子类中

重构为:

动机

你发现类中的某些行为只被一部分实例用到,其他实例并不需要它们。

有时候这种行为上的差异是通过类型码来区分的,此时你可以使用 Replace Type Code with Subclass Replace Type Code with Sate/Strategy

当然可以考虑 Extract Class 或者 Extract Subclass,两者之间的抉择其实就是委托和继承之间的抉择。Extract Subclass通常更容易进行,但它也有限制,一旦对象创建完成,你无法再改变与类型相关的行为。但如果使用 Extract Class,你只需要插入另一组件就可以改变对象的行为。

做法

class Employee

.h

class Employee{
public:
    Employee(){}
    Employee(int rate);
    ~Employee(){}
    int getRate();
private:
    int _rate;
};

.cpp 

Employee::Employee(int rate){
    _rate = rate;
}
int Employee::getRate(){
    return _rate;
}

class JobItem  决定当地修车厂的工作报价

.h

class JobItem{
public:
    JobItem(int unitPrice,int quantity,bool isLabor,Employee employee);
    int getTotalPrice();
    int getUnitPrice();
    int getQuantity();
    Employee getEmployee();
private:
    int _unitPrice;
    int _quantity;
    Employee _employee;
    bool _isLabor;
};

.cpp 

JobItem::JobItem(int unitPrice,int quantity,bool isLabor,Employee employee){
    this->_unitPrice = unitPrice;
    this->_quantity = quantity;
    this->_isLabor = isLabor;
    this->_employee = employee;
}
int JobItem::getTotalPrice(){
    return getUnitPrice() * _quantity;
}
int JobItem::getUnitPrice(){
    return (_isLabor) ? _employee.getRate() : _unitPrice;
}
int JobItem::getQuantity(){
    return _quantity;
}

现在提炼一个LaborItem子类,因为上述某些行为和数据只在按工时(Labor)收费的情况下才需要,下面将Labor相关的内容全部下移

.h

class LaborItem : public JobItem{
public:
    LaborItem(int unitPrice,int quantity,bool isLabor,Employee employee);
}

.cpp 

LaborItem::LaborItem(int unitPrice,int quantity,bool isLabor,Employee employee):JobItem(unitPrice,quantity,isLabor,employee){
    
}

下面找到JobItm构造函数调用的时候:

    Employee kent(0.5);
    JobItem * j1 = new JobItem(0,5,true,kent);

修改为:

JobItem * j2 = new LaborItem(0,5,true,kent);

下面把 isLabor 字段进行替换

class Jobitem

.h

class JobItem{
public:
    int getTotalPrice();
    int getUnitPrice();
    int getQuantity();
    Employee getEmployee();
    ~JobItem(){}
    JobItem(int unitPrice,int quantity,Employee * employee);
protected://修改可见度
    Employee * _employee;
private:
    int _unitPrice;
    int _quantity;
};

.cpp

JobItem::JobItem(int unitPrice,int quantity,Employee * employee){
    this->_unitPrice = unitPrice;
    this->_quantity = quantity;
    this->_employee = employee;
}

int JobItem::getTotalPrice(){
    return getUnitPrice() * _quantity;
}
int JobItem::getUnitPrice(){
    return _unitPrice;
}
int JobItem::getQuantity(){
    return _quantity;
}

class laborItem

.h

class LaborItem : public JobItem{
public:
    LaborItem(int unitPrice,int quantity,Employee * employee);
    Employee * getEmployee();
    int getUnitPrice();
};

.cpp 

LaborItem::LaborItem(int unitPrice,int quantity,Employee * employee):JobItem(unitPrice,quantity,employee){

}
Employee * LaborItem::getEmployee(){
    return _employee;
}
int LaborItem::getUnitPrice(){
    return _employee->getRate();
}

当只按照零件收费的时候才会用到 _uintPrice 字段进行收费。 

可以将这个字段也提炼到一个子类中,让 JobItem 完全变成一个抽象类。

1.7Extract Superclass(提炼超类)

两个类有相似特性

为这两个类建立一个超类,将相似特性移至超类

动机

重复代码是系统中最糟糕的东西之一。如果你在不同地方做同一件事情,一旦需要修改哪些动作,那么就要修改很多的地方

重复代码的某种形式就是:两个类似相同的方式做相似的事情,或者以不同的方式做类似的事情。

对象提供了一种简化这种情况的机制,有效的使用继承,即可避免这种问题。

做法

下面例子中,以Employee表示员工,以Department表示部门

class Employee

.h

class Employee{
public:
    Employee(QString name,QString id,int annualCost);
    int getAnnualCost();
    QString getId();
    QString getName();
private:
    QString _name;
    int _annualCost;
    QString _id;
};

.cpp 

Employee::Employee(QString name,QString id,int annualCost){
    _name = name;
    _id = id;
    _annualCost = annualCost;
}
int Employee::getAnnualCost(){
    return this->_annualCost;
}
QString Employee::getId(){
    return this->_id;
}

class Department

.h

class Department{
public:
    Department(QString name);
    int getTotalAnnualCost();
    int getHeadCount();
    Employee * getStaff(int place);
    void addStaff(Employee * arg);
    QString getName();
private:
    QString _name;
    QVector<Employee *> _staff;
};

.cpp

Department::Department(QString name){
    this->_name = name;
}
int Department::getTotalAnnualCost(){
//    Employee * e = getStaff();
    int result = 0;
//    while(){

//    }
    return result;
}
int Department::getHeadCount(){
    return _staff.size();
}
Employee * Department::getStaff(int place){
    return _staff[place];
}
void Department::addStaff(Employee * arg){
    _staff.append(arg);
}
QString Department::getName(){
    return this->_name;
}

这里面有两个共同点,部门和员工都有名称;其实,它们都有年度成本,只不过计算方式不一样

其次,它们都有年度成本,只不过计算方式略微不同。现在要提取出一个超类,包容这些共同特性

首先将_name字段和getName()方法进行上移

class Party

.h

class Party{
public:
    QString getName();
protected:
    Party(QString name);
    QString _name;
private:

};

.cpp

Party::Party(QString name){
    this->_name = name;
}
QString Party::getName(){
    return _name;

class Employee

Employee::Employee(QString name,QString id,int annualCost):Party(name){
//    _name = name;//使用父类构造函数
    _id = id;
    _annualCost = annualCost;
}

class Department

Department::Department(QString name):Party(name){
}

下面我们将处理 getTotalAmount 和 getAnnualCost 两个函数

因为算法各不相同,那么就在父类Party中定义一个抽象函数

class Party{
public:
    QString getName();
    virtual int getAnnualCost() = 0;
    virtual ~Party(){}
protected:
    Party(QString name);
    QString _name;
private:

};

至于后续,可以使用其他设计模式进一步对提高代码的复用率 

1.8Extract Interface(提炼接口)

待更新

1.9Collapse Hierarchy(折叠继承体系)

父类和子类之间无太大区别

将它们融为一体

 

1.10Form Template Method(塑造模板函数)

待更新

1.11Replace Inheritance with Delegation(以委托取代继承)

某个子类只能使用父类接口中的一部分,或是根本不需要继承而来的数据

在子类中新建一个字段用以保存父类,调整子类函数,令它委托父类

然后去掉两者之间的继承关系

动机

当出现下面的情况时:一开始继承了一个类,然后发现父类中的许多操作并不真正属于子类。

换一种说法,就是从父类中继承了一大堆不需要的东西

可以考虑以 委托 取代继承

做法

此方法也是各大设计模式中最常用的方法之一

class Person{
    
};

class Employee : public Person{
    
};

重构为:

class Person{
    
};

class Employee{
private:
    Person * Object = new Person;
};

1.12Replace Delegation with Inheritance(以继承取代委托)

你在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数

废了那么大劲儿也委托函数,倒不如老老实实的继承好了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值