重构(Martin Fowler)——重新组织数据

核心内容源于《重构 改善既有代码的设计》(Refactoring Improving the Design of Existing Code——Martin Fowler著)。

本章将会介绍几个能够轻松处理数据都重构手法。

Part1:

关于“对象应该直接访问其中的数据,还是应该通过访问函数来访问数据”一直有争议,当需要通过函数去访问数据时,可使用Self Encapsulate Field

Part2:

面对对象语言有一个很有用的特征:除了允许使用传统语言提供的简单数据结构类型,它们还允许你定义新类型。

Replace Value with Object 可以更好的改善对象的表达,如果有太多地方需要这个对象,使用Change Value of Reference将它们变成引用对象

Part3:

如果你看到一个数组的行为方式很像一个数据结构,可以使用Replace Array with Object把数组变成对象

从而使这个数据结构更清晰地显露出来,之后使用Move Method为这个新对象加入相应行为

Part4:

如果出现特殊意义的数字,使用Replace Magic Number with Symbolic Constant将其删除 

Part5:

对象之间的关联可以是单向的,也可以是双向的。单向关联比较简单,但是有时候需要使用Change Unidirectional Association to Bidirectional将它变成双向关联。

Change Bidirectional  to Unidirectional Association则刚好相反。

Part6:

GUI类竟然去处理不该它们处理的业务逻辑。为了把这些业务逻辑的行为移到合适的领域类去,你需要在领域类中保存这些逻辑的相关数据

并运用Duplicate Observed Data 提供GUI的支持。

Part7:

面对对象变成的关键原则之一,如果一个类公开了任何public数据,应该使用Encapsulate Field将它郑重地包装起来。

如果被公开的数据是个集合,就应该使用Encapsulate Collection,因集合有特殊协议,如果一整条记录都被裸露在外,应该使用Replace Record with Data Class

Part8:

有一种特殊数据,类型码(type code):这是一种特殊数值。用来指出“与实例所属之类型相关的某些东西”。

可以使用Replace Type Code with Class将其替换。

目录

1.1Self Encapsulate Field(自封装字段)

动机

做法

1.2Replace Data Value with Object(以对象取代数据值)

动机

做法

1.3Change Value to Reference(将值对象改为引用对象)

动机

做法

1.4Change Reference to Value(将引用对象改为值对象)

1.5Replace Array with Object(以对象取代数组)

动机

做法

1.6Duplicate Observed Data(复制“被监视数据”)

1.7ChangeUnidirectional  to Bidirectional Association(将单向关联改为双向关联)

动机

做法

1.8Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) 

动机

1.9Replace Magic Number with Symbolic Constant(以字面常量取代魔法数)

1.10Encapsulate Field(封装字段)

1.11Encapsulate Collection(封装集合)

动机

做法

1.12Replace Record with Data Calss(以数据类取代记录)

动机

1.13Replace Type Code with Class(以类取代类型码)

动机

做法

1.14Replace Type Code with Subclasses(以子类取代类型码)

动机

1.15Replace Type Code with State/Strategy(以State/Strategy取代类型码)

动机

1.16Replace Subclass with Field(以字段取代子类)

动机

做法


 

1.1Self Encapsulate Field(自封装字段)

你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙

为这个字段建立取值/设值函数,并且只以这些函数来访问字段

class Text{
private:
    int _low;
    int _high;
public:
    bool includes(int arg){
        return arg >= _low && arg <=_high;
    }
};

重构为:

class Text{
private:
    int _low;
    int _high;
public:
    bool includes(int arg){
        return arg >= getLow() && arg <= getHigh();
    }
    int getLow(){
        return _low;
    }
    int getHigh(){
        return _high;
    }
};

动机

“字段访问方式”这个问题上,有两种截然不同的观点

其中一派任务,在该变量定义所在的类中,你可以自由访问它。

另一派认为,即使在这个类中你也应使用函数进行间接访问。

归根结底,间接访问变量的好处是,子类可以通过覆写一个函数而改变获取数据的途径;它还支持更灵活的数据管理方式,例如延迟初始化(只有某个值达到特定需求时,才对它进行初始化)

直接访问的好处是显而易见的,代码比较容易阅读

当子类需要override的时候,还是间接访问变量更加合适

做法

现在有类IntRange

class IntRange{
private:
    int _low;
    int _high;

public:
    bool includes(int arg){
        return arg >= getLow() && arg <= getHigh();
    }
    void grow(int factor){
        _high = _high * factor;
    }
    IntRange(int low,int high){
        _low = low;
        _high = high;
    }

};

现在对数据进行再次封装

class IntRange{
private:
    int _low;
    int _high;


public:
    bool includes(int arg){
        return arg >= getLow() && arg <= getHigh();
    }
    void grow(int factor){
        setHigh(getHigh() * factor);
    }

    int getLow(){
        return _low;
    }
    int getHigh(){
        return _high;
    }

    void setLow(int arg){
        _low = arg;
    }
    void setHigh(int arg){
        _high = arg;
    }

};

关于构造函数:

    IntRange(int low,int high){
        initialize(low.high);
    }
private:
    void initialize(int low,int high){
        _low = low;
        _high = high;
    }

一旦你拥有一个子类,上述所有动作的价值就体现出来了!

class CappedRange:public IntRange{
public:
    CappedRange(int low,int high,int cap){
        _cap = cap;
        _intRange = new IntRange(low,high);
    }

    int getHigh(){
        return min(this->_intRange->getHigh(),getCap());
    }
private:
    int _cap;
    IntRange * _intRange;
    int getCap(){
        return _cap;
    }
};

如果我要在CappedRange中覆写getHigh(),从而加入对“范围上限”(cap)的考虑,而不必修改IntRange的任何行为。

1.2Replace Data Value with Object(以对象取代数据值)

你有一个数据项,需要与其他数据和行为一起使用才有意思

将数据项变成对象

动机

有时你往往决定以简单的数据项表示简单的情况,但是随着开发的进行,你会发现,数据项不再简单

例如,一开始使用字符串表示电话号码,随着事务的发展,电话号码需要格式,区号等等属性。

当出现Duplicate Code和Feature Envy的情况时,就需要把数据用对象取代。

做法

现在有Order类如下:

class Order{
public:
    Order(string customer){
        _customer = customer;
    }
    string getCustomer(){
        return _customer;
    }
    void setCustomer(string arg){
        _customer = arg;
    }
private:
    string _customer;
};

现在将_customer字段拿出来,变成Customer类:

class Customer{
public:
    Customer(string name){
        _name = name;
    }
    string getName(){
        return _name;
    }
private:
    string _name;
};

现在修改Order类:

class Order{
public:
    Order(string customerName){
        _customer = new Customer(customerName);
    }
    string getCustomerName(){
        return _customer->getName();
    }
    void setCustomer(string customerName){
        _customer = new Customer(customerName);
    }
private:
    Customer * _customer;
};

以上仅仅是举例,当需要的字段多了就能体现该方法的优势,某种意义上还是尽可能的让一个类责任清晰,不要过于冗余

1.3Change Value to Reference(将值对象改为引用对象)

你从一个类衍生出许多彼此相等的实例,希望将它们替换为同一对象

将这个值对象变成引用对象

 

动机

在许多系统中,你都可以对对象做一个有用的分类:引用对象和值对象。

前者就像“客户”,“账户”这样的东西,每个对象都代表真实世界中的一个实物,你可以直接以相等操作符检查两个对象是否相等。

后者像“日期”,“钱”这样的东西,完全由其数值来定义

 

做法

(等待更新)

1.4Change Reference to Value(将引用对象改为值对象)

(等待更新)

 

1.5Replace Array with Object(以对象取代数组)

你有一个数组,其中的元素各自代表不同的东西

以对象替换数组,对于数组中的每个元素,以一个字段来表示

    string row[3];
    row[0] = "Liverpool";
    row[1] = "15";

重构为:

    Performance row = new Performance();
    row.setName("Liverpool");
    row.setWins("15");

动机

数组是一个常见的用以组织数据的结构,不过,它应该是以“某种顺序容纳一组相似对象”而存在的,一旦不符合这要求,显然需要对这个部分重新进行设计。

做法

建立Performance类:

class Performance{
    string getName(){
        return _data[0];
    }
    void setName(string arg){
        _data[0] = arg;
    }
    int getWins(){
        return _data[1];
    }
    void setWins(string arg){
        _data[1] = arg;
    }
private:
    string _data[3];
};

显然,我们可以完成下面代码的重构:

    string row[3];
    row[0] = "Liverpool";
    row[1] = "15";

    Performance row = new Performance();
    row.setName("Liverpool");
    row.setWins("15");

下面对Performance类进一步处理:

class Performance{
    string getName(){
        return _name;
    }
    void setName(string arg){
        _name = arg;
    }
    int getWins(){
        return _win;
    }
    void setWins(string arg){
        _win = arg;
    }
private:
    string _name;
    string _win;
};

 

1.6Duplicate Observed Data(复制“被监视数据”)

你有一些领域数据置身于GUI空间中,而领域函数需要访问这些数据

将这些数据复制到一个领域对象中。建立一个Observable模式,用以同步领域对象和GUI对象内的重复数据

 

1.7ChangeUnidirectional  to Bidirectional Association(将单向关联改为双向关联)

两个类都需要使用对方特性,但其间只有一条单向链接

添加一个反向指针,并使修改函数能够同时更新两条链接

 

动机

本重构手法只使用反向指针实现双向关联。其他技术(例如连接对象)需要其他重构手法。

做法

现在有两个类,表示“订单”的Order和表示“客户”的Customer

Order引用了Customer,Customer并没有引用Order,显然,现在是单向的!

class Order{
public:
    Customer getCustomer(){
        return _customer;
    }
    void setCustomer(Customer arg){
        _customer = arg;
    }
private:
    Customer _customer;
};

下面我们将单向的连接变成双向。

 建立连接,添加辅助函数

现在为Customer添加一个字段,一个客户可以拥有多份订单

在Customer客户类中,有一个order订单集合,即为反向指针:

list<Order *> _order;

①如果两者都是引用对象,而其间的关联是“一对多”关系,那么就由“拥有单一引用”的那一方承担“控制者”角色。

   以本例而言,如果一个客户可拥有多份订单,一份订单只能属于一个客户,那么订单就是“拥有单一引用”的那一方,由Order类(订单)来控制关联关系

②如果某个对象是组成另一对象的部件,那么由后者控住关联关系

③如果两者都是引用对象,其间是“多对多”关系,那么随意处理

现在必须在Customer添加一个辅助函数,让Order可以直接访问_order集合

class Customer{
public:
    //....
    list<Order *> friendOrders(){
        return _order;
    }

private:
    string _name;
    list<Order *> _order;
};

friendOrders只能被设置为public,private和protected其余类均不可见

Order中更新修改函数

现在更新setCustomer,令其同时更新反向指针

class Order{
public:
    Order(){
        _customer = new Customer();
    }
    Customer * getCustomer(){
        return this->_customer;
    }
    void setCustomer(Customer * arg){
        if(_customer != nullptr){
            _customer->friendOrders().remove(this);
        }
        _customer = arg;
        if(_customer != nullptr){
            _customer->friendOrders().push_back(this);
        }
    }
private:
    Customer * _customer;
};

 Customer中增加控制函数

如果你希望能在Customer中也能修改,那么就调用其控制函数

class Customer{
public:
    //.....
    void addOrder(Order * arg){
        arg->setCustomer(this);
    }
    //.....
};

 现在我们来看看完整版本:

class Order
{
public:
    Order(){
        _customer = new Customer();
    }
    Customer * getCustomer(){
        return this->_customer;
    }
    void setCustomer(Customer * arg){
        if(_customer != nullptr){
            qDebug()<<"Nullptr";
            _customer->friendOrders().remove(this);//将订单从Customer类中的订单列表中移除
        }
        _customer = arg;//订单中的客户进行更新
        if(_customer != nullptr){
            _customer->friendOrders().push_back(this);//将订单加入到Customer类中的订单列表中
        }
    }
private:
    Customer * _customer;
};

 class Customer

class Customer{
public:
//    explicit Customer(QWidget *parent = nullptr);
    Customer(){}
    Customer(string name){
        _name = name;
    }
    string getName(){
        return _name;
    }
    //此处需要注意,返回的是引用,需要按地址(引用)传递,而不是按值传递
    list<Order *> & friendOrders(){
        return  _orders;
    }
    void printOrdersItem(){
        for(auto item:_orders){
            qDebug()<<item;
        }
    }
private:
    string _name;
    list<Order *> _orders;
};

最初的状态是Customer找那个不包含有Order,现在是Customer和Order相互包含

以上是一对多的版本,一个客户(Customer)对应多个订单(Order),所有才有list<Order *> _orders;的存在。

站在订单(Order)的角度,一份订单(Order)只对应一个客户(Customer),所以订单(Order)充当控制者,

1.8Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) 

两个类之间有双向关联,但其中一个类如今不需要另一个类的特性

去除不必要的关联

动机

双向关联很有用,但维护需要耗费大量的精力。

双向关联迫使两个类之间有了依赖,对其中任一个类的任何修改,都可能引发另一个类的变化。

当没有必要使用双向依赖,应该去掉其中不必要的关联

1.9Replace Magic Number with Symbolic Constant(以字面常量取代魔法数)

你有一个字面数值,带有特别含义

创造一个常量,根据其意义为它命名,并将上述的字面数值替换为这个常量

double potentialEnergy(double mass,double height){
    return mass * 9.81 * height;
}

9.81意义不明,可读性差,一旦变化,你就要找到全部的9.81,非常不方便(这种被称为魔法数 

现在进行重构:

double CONSTANT = 9.81;
double potentialEnergy(double mass,double height){
    return mass * CONSTANT * height;
}

任何语言都允许你声明常量。常量不会造成任何性能开销,却可以大大提高代码的可读性。

1.10Encapsulate Field(封装字段)

你的类中存在一个public字段

将它声明为private,并提供相应的访问函数

public:
    string _name;

重构为:

private:
    string _name;
public:
    string getName(){
        return _name;
    }
    void setName(string arg){
        return _name;
    }

按照封装的原则,是绝对不能把数据声明为public的,否则其他对象就有可能访问甚至修改这项数据,而拥有该数据的对象却毫无察觉。

 

1.11Encapsulate Collection(封装集合)

有个函数返回一个集合

让这个函数返回该集合的一个只读副本,并在这个类中提供添加/删除集合元素的函数

 

动机

我们常常在一个类中使用集合(数组,链表,树等)来保存一组实例。这样的类通常也会提供对集合的取值/设值函数。

但是集合的处理方式应该和其他的数据类型略有不同。

①取值函数不该返回集合本身,因为这会让用户得以修改集合内容而集合拥有者一无所知。也会对用户暴露过多对象内部数据结构的信息。

②如果一个取值函数确实需要返回对个值,他应该避免用户直接操作对象内所保留的集合,并隐藏对象内与用户无关的数据结构。

不应该为集合提供一个设值函数(set.....),应该提供用为集合添加/移除元素的函数。

 

做法

现在有两个类

Class Course:

class Course{
public:
    Course(string name,bool inAdanced){
        _courseName = name;
        _inAdanced = inAdanced;
    }
    bool isAdvanced(){
        return _inAdanced;
    }
    string getCourseName(){
        return _courseName;
    }
private:
    string _courseName;
    bool _inAdanced;
};

Class Person:

class Person{
public:
    Person(){}
    list<Course *> & getCourses(){
        return _courses;
    }
    void setCourses(list<Course *> arg){
        _courses = arg;
    }
    void myAdvanceCourse(){
        for(auto item:_courses){
            if(item->isAdvanced() == true) {
                cout<<item->getCourseName();
            }
        }
    }
private:
    list<Course *> _courses;

};

有了上面两个和对应的接口,我们下面为kent添加一些课程。

    Person * kent = new Person();
    list<Course *> _m;
    _m.push_back(new Course("Smalltalk",false));//客户名称 课程等级(false 低级 true 高级)
    _m.push_back(new Course("Appreciating",true));
    kent->setCourses(_m);

    Course * refact = new Course("Math",false);
    kent->getCourses().push_back(refact);//此处不满足封装的要求

    kent->myAdvanceCourse();

输出:

现在我们要做的,就是为Person类增合适的修改函数并更具情况,将set函数变成initial或者replace函数:

按照集合封装的原则,我们将Person类进行改写:

class Person : public QWidget{
public:
    Person(){}
    //删除取值函数,或者不返还引用,只读不可写
//    list<Course *> & getCourses(){
//        return _courses;
//    }

    //设值函数;setCourse
//    void setCourse(list<Course *> arg){
//        _courses = arg;
//    }
    //初始化集合
    void initialzeCourses(list<Course *> & arg){
        for(auto item : arg){
            _courses.push_back(item);
        }
    }
    //修改替换
    void replaceCourses(Course * oldArg,Course * newArg){
        if(!oldArg||!newArg) return;
        _courses.remove(oldArg);
        _courses.push_back(newArg);
    }
    void myAdvanceCourse(){
        for(auto item:_courses){
            if(item->isAdvanced() == true) {
                cout<<item->getCourseName();
            }
        }
    }
    //添加 修改函数
    void addCourse(Course * arg){
        _courses.push_back(arg);
    }
    void removeCourse(Course * arg){
        _courses.remove(arg);
    }
private:
    list<Course *> _courses;
};

增加了修改函数,对set设值函数进行了修改,删除或改进取值函数

下面我们对具体操作代码进行修改

    Person * kent = new Person();
    list<Course *> _m;
    _m.push_back(new Course("Smalltalk",false));//客户名称 课程等级(false 低级 true 高级)
    _m.push_back(new Course("Appreciating",true));
    kent->initialzeCourses(_m);
    Course * refact = new Course("Math",false);
    kent->addCourse(refact);
    kent->myAdvanceCourse();

集合被完美的封装了,用户无法进行直接操作

1.12Replace Record with Data Calss(以数据类取代记录)

你需要面对传统编程环境中的记录结构

为该记录创建一个“哑”数据对象

动机

记录型结构是许多编程环境的共同性质。

在一些特殊情况下,需要处理一些外来数据,需要把这些数据放入一个接口类中,之后在这个类中增加相应的设值/取值函数

 

1.13Replace Type Code with Class(以类取代类型码)

类之中有一个数值类型码,但它并不影响类的行为

以一个新的类替换该数值类型码

动机

在以C为基础的编程语言中,类型码或枚举类很常见。

但是带着一个有意义的符号名,类型码可读性就会很一般。

做法

现在有类Person

class Person{
public:
    Person(int bloodGroup){
        this->_bloodGroup = bloodGroup;
    }
    void setBloodGroup(int arg){
        this->_bloodGroup = arg;
    }
    int getBloodGroup(){
        return _bloodGroup;
    }
private:
    int O = 0;
    int A = 1;
    int B = 2;
    int AB = 3;  
    int _bloodGroup;
};

首先建立一个BloodGroup类,用以表示“血型”,并在这个类实例中保存原来的类型码数值,参考单例模式

因为类或者结构体的前向声明只能用来定义指针对象或者引用,因为编译到的时候并没有发现定义,不知道类或者结构的内部情况,没办法具体的构造一个对象,所以会报错

class BloodGroup如下:

class BloodGroup{
public:
    //此处为前向声明只能是指针或者引用,不能是其他,因为编译器不知道类的作用。
    static BloodGroup * O;
    static BloodGroup * A;
    static BloodGroup * B;
    static BloodGroup * AB;
    int getCode(){
        return _code;
    }
private:
    int _code;
    //构造函数
    BloodGroup(int code){
        _code = code;
    }
};

.cpp中

BloodGroup * BloodGroup::O = new BloodGroup (0);
.....

class Person

class Person{
public:
    Person(BloodGroup * bloodGroup){
        this->_bloodGroup = bloodGroup;
    }
    void setBloodGroup(BloodGroup * arg){
        this->_bloodGroup = arg;
    }
    BloodGroup * getBloodGroup(){
        return _bloodGroup;
    }
private:
    BloodGroup * _bloodGroup;
};

具体实际应用: 

    Person * _test = new Person(BloodGroup::A);
    cout<<_test->getBloodGroup()->getCode();

将Type Code直接变成class,而该类参考单例模式的写法,只能有O、A、B、AB四种类型可以使用作用域进行访问

同样,上述内容依旧可以进行重构,增加“隐藏关系”。

class Person{
public:
    Person(BloodGroup * bloodGroup){
        this->_bloodGroup = bloodGroup;
    }
    void setBloodGroup(BloodGroup * arg){
        this->_bloodGroup = arg;
    }
    //也可以委托一层隐藏关系
//    BloodGroup * getBloodGroup(){
//        return _bloodGroup;
//    }
    int getBloodGroupCode(){
        return this->_bloodGroup->getCode();
    }
private:
    BloodGroup * _bloodGroup;
};

//...........
    Person * _test = new Person(BloodGroup::A);
    cout<<_test->getBloodGroupCode();

 

1.14Replace Type Code with Subclasses(以子类取代类型码)

你有一个不可变的类型码,他会影响类的行为(类型码是类中条件表达式的判断条件)

以子类取代这个类型码

 

动机

当你面对的类型码不会影响宿主类的行为,可以放心的使用Replace Type Code with Class

但是如果类型码会影响宿主类的行为,那么最好借助多台来处理变化行为

一般情况这种类型码都是出现在switch和if-then-else结构,不论哪儿种形式,都是根据类型码的不同执行不同的动作。

此时,让宿主类为基类,更加每一种情况建立子类

但是有两种情况不能使用此重构手段:

  1. 类型码值在对象创建之后发生了改变
  2. 由于某些原因,类型码宿主类已经有了子类,那么

Replace Type Code with Subclasses 的主要作用其实是搭建一个舞台,让Replace Conditional with Polymorphism 得以发挥作用

如果宿主类中并没有出现条件表达式,那么Replace Type Code with Class 更合适,风险也更低。

使用Replace Type Code with Subclasses 的好处在于:它把“对不同行为的了解”从类用户哪儿转移到类自身,如果需要再加入新的行为变化,只需要添加子类即可。

原始版本的class Employee

class Employee{
private:
    int _type;
    Employee(int type){
        _type = type;
    }
protected:
    static int ENGINEER;
    static int SALESMAN;
    static int MANAGER;
public:
    //建立一个工厂函数
    static Employee * create(int type){
        if(type == ENGINEER){
            //.....
        }
        else{
            return new Employee(type);
        }
    }
    int getType(){return _type;}
};

现在,为Employee添加一个常量类:

class occupation{
public:
    constexpr static int getEngineer(){
        return ENGINEER;
    }
    constexpr static int getSalesman(){
        return SALESMAN;
    }
    constexpr static int getManager(){
        return MANAGER;
    }

private:
    const static int ENGINEER = 0;
    const static int SALESMAN = 1;
    const static int MANAGER = 2;
};

.h文件

class Employee{
private:
    int _type;
    Employee * _employee = nullptr;
protected:
    Employee(int type){
        _type = type;
    }
public:
    //建立一个工厂函数
    static Employee * create(int type);
    virtual int getType() = 0;//做一个纯虚函数
    virtual ~Employee(){}
};

.cpp 一下工厂函数的实现是关键,体现了Replace Type Code with Subclasses 的精髓

Employee * Employee::create(int type){
    switch(type){
    case occupation::getEngineer():
            return new Engineer();
        break;
    case occupation::getSalesman():
            return new Salesman();
        break;
    case occupation::...:
            return new Manager();
        break;
    default:
        return nullptr;
    }
}

原版类型码对应的子类:

class Engineer : public Employee{
public:
    Engineer(int tyep = occupation::getEngineer()):Employee(tyep){}
    virtual int getType(){
        return occupation::getEngineer();
    }
};

class Salesman : public Employee{
public:
    Salesman(int tyep = occupation::getSalesman()):Employee(tyep){}
    virtual int getType(){
        return occupation::getSalesman();
    }
};

class Manager : public Employee{
public:
    Manager(int tyep = occupation::getManager()):Employee(tyep){}
    virtual int getType(){
        return occupation::getManager();
    }
};

此部分还可以进一步重构,方法见Replace Parameter with Explicit Methods(简化函数调用)

1.15Replace Type Code with State/Strategy(以State/Strategy取代类型码)

你有一个类型码,他会影响类的行为,但是你无法通过继承手段消除它

以状态对象取代类型码

动机

本项重构和Replace Type Code with Subclasses很相似,但如果“类型码的值在对象生命期中发生变化”或“其他原因使得宿主类不能被继承”。

可以使用State或Strategy模式[Gang of Four]

 

1.16Replace Subclass with Field(以字段取代子类)

你的各个子类的唯一差别只在“返回常量数据”的函数身上

修改这些函数,使它们返回超类的某个字段,然后销毁子类

动机

建立子类的目的,是为了增加新特性或变化其行为。

有一种变化行为被称为“常量函数(constant method)”,它们会返回一个硬编码的值。

其用途:你可以让不同的子类中的同一个访问函数返回不同的值。

你可以在超类中将访问函数声明为抽象函数,并在不同的子类中让它返回不同的值。

但是如果子类中只有常量函数,没有多少存在价值。

可以在超类中设计一个与常量函数返回值相应的字段,从而完全去除这样的子类,如此一来就可以避免因继承而带来的额外复杂性了。

做法

下面getCode()就是硬编码函数

class Person{
protected:
    virtual bool isMale() = 0;
    virtual char getCode() = 0;
};

class Male : public Person{
    virtual bool isMale(){
        return true;
    }
    virtual char getCode(){
        return 'M';
    }
};

class Female:public Person{
    virtual bool isMale(){
        return false;
    }
    virtual char getCode(){
        return 'F';
    }
};

两个子类的唯一不同就是用不同的方式实现了Person所声明的抽象函数getCode()

现在应该把这两个慵懒的子类去掉

为父类建立工厂函数

class Person{
protected:
    virtual bool isMale() = 0;
    virtual char getCode() = 0;
public:
    //建立工厂函数
    static Person * createMale();
    static Person * createFeMale();
};

将调用子类的地方全部跟换为调用工厂函数

    Person * kent = new Male();//Before
    Person * kent = Person::createMale();//After

下面,将子类中的常量函数搬运到父类,为此要建立新的字段和构造函数

class Person{
private:
    bool _isMale;
    char _code;
protected:
    //删除子类的常量函数,在父类中添加
//  virtual bool isMale();
    bool isMale(){
        return _isMale;
    }
//    virtual char getCode() = 0;
    char getCode(){
        return _code;
    }
    Person(bool isMale,char code){
        _isMale = isMale;
        _code = code;
    }
public:
    //建立工厂函数
    static Person * createMale();
    static Person * createFeMale();
};

下面再更改工厂函数

Person * Person::createMale(){
    return new Person(true,'M');
}
Person * Person::createFeMale(){
    return new Person(false,'F');
}

重构完成

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
重构,一言以蔽之,就是在不改变外部行为的前提下,有条不紊地改善代码。多年前,正是本书原版的出版,使重构终于从编程高手们的小圈子走出,成为众多普通程序员日常开发工作中不可或缺的一部分。本书也因此成为与《设计模式》齐名的经典著作,被译为中、德、俄、日等众多语言,在世界范围内畅销不衰。 本书凝聚了软件开发社区专家多年摸索而获得的宝贵经验,拥有不因时光流逝而磨灭的价值。今天,无论是重构本身,业界对重构的理解,还是开发工具对重构的支持力度,都与本书最初出版时不可同日而语,但书中所蕴涵的意味和精华,依然值得反复咀嚼,而且往往能够常读常新。 第1章 重构,第一个案例 1.1 起点 1.2 重构的第一步 1.3 分解并重组statement() 1.4 运用多态取代与价格相关的条件逻辑 1.5 结语 第2章 重构原则 2.1 何谓重构 2.2 为何重构 2.3 何时重构 2.4 怎么对经理说 2.5 重构的难题 2.6 重构与设计 2.7 重构与性能 2.8 重构起源何处 第3章 代码的坏味道 3.1 Duplicated Code(重复代码) 3.2 Long Method(过长函数) 3.3 Large Class(过大的类) 3.4 Long Parameter List(过长参数列) 3.5 Divergent Change(发散式变化) 3.6 Shotgun Surgery(霰弹式修改) 3.7 Feature Envy(依恋情结) 3.8 Data Clumps(数据泥团) 3.9 Primitive Obsession(基本类型偏执) 3.10 Switch Statements(switch惊悚现身) 3.11 Parallel InheritanceHierarchies(平行继承体系) 3.12 Lazy Class(冗赘类) 3.13 Speculative Generality(夸夸其谈未来性) 3.14 Temporary Field(令人迷惑的暂时字段) 3.15 Message Chains(过度耦合的消息链) 3.16 Middle Man(中间人) 3.17 Inappropriate Intimacy(狎昵关系) 3.18 Alternative Classes with Different Interfaces(异曲同工的类) 3.19 Incomplete Library Class(不完美的库类) 3.20 Data Class(纯稚的数据类) 3.21 Refused Bequest(被拒绝的遗赠) 3.22 Comments(过多的注释) 第4章 构筑测试体系 4.1 自测试代码的价值 4.2 JUnit测试框架 4.3 添加更多测试 第5章 重构列表 5.1 重构的记录格式 5.2 寻找引用点 5.3 这些重构手法有多成熟 第6章 重新组织函数 6.1 Extract Method(提炼函数) 6.2 Inline Method(内联函数) 6.3 Inline Temp(内联临时变量) 6.4 Replace Temp with Query(以查询取代临时变量) 6.5 Introduce Explaining Variable(引入解释性变量) 6.6 Split Temporary Variable(分解临时变量) 6.7 Remove Assignments to Parameters(移除对参数的赋值) 6.8 Replace Method with Method Object(以函数对象取代函数) 6.9 Substitute Algorithm(替换算法) 第7章 在对象之间搬移特性 7.1 Move Method(搬移函数) 7.2 Move Field(搬移字段) 7.3 Extract Class(提炼类) 7.4 Inline Class(将类内联化) 7.5 Hide Delegate(隐藏“委托关系”) 7.6 Remove Middle Man(移除中间人) 7.7 Introduce Foreign Method(引入外加函数) 7.8 Introduce Local Extension(引入本地扩展) 第8章 重新组织数据 8.1 Self Encapsulate Field(自封装字段) 8.2 Replace Data Value with Object(以对象取代数据值) 8.3 Change Value to Reference(将值对象改为引用对象) 8.4 Change Reference to Value(将引用对象改为值对
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值