当你在一个类中使用字段的时候,发现这个字段必须要和其他数据或者行为一起使用才有意义。你就应该考虑把这个数据项改成对象。在开发初期,我们对于新类中的字段往往会采取简单的基本类型形式来保存,但随着我们开发进度的增加,这些简单的数据项就不再那么简单了。比如一开始你会使用一个字符串来表示一串电话号码,但是随后你会发现,这个电话号码已经变的不再纯粹,它可能还需要“格式化”,“抽取取号”等特殊行为。一开始你可能会不以为意,觉得这个数据项就这么一两个,不会对你造成影响。但重复代码(Duplicate Code)和依恋情结(Feature Envy)这两个坏味道会很快就散发出来。什么样的行为就应该放在什么样的类中去,当这些坏味道出现的时候你就应该将数据值改为对象了。

  • 做法:
  • 为待替换数值新建一个类,并在这个新类中新建一个const字段(Java final)并保持其类型和源类中你需要替换的数值类型一样,然后在新类中加入一个这个字段的取值函数(get),并加上一个接受此字段为参数的构造函数。
  • 编译。
  • 将源类中待替换数值的类型改为你前面新建的类类型。
  • 修改源类中关于这个字段的取值函数,令他调用新类的取值函数。
  • 如果源类构造函数中用到这个待替换字段(多半是赋值动作)你就应该修改构造函数,让它变为用新类的构造函数来给这个字段赋值。
  • 修改源类中待替换字段的设值函数(set)令他为新类创建一个实例。
  • 编译,测试。
  • 现在,你可能还会对新类使用Change Value to Reference。

例子:

class Order
{
public:
Order(const QString &customer) :
m_customer = customer; QString customer()
{
return m_customer;
} void setCustomer(const QString &customer)
{
m_customer = customer;
}
private:
QString m_customer;
};

我们可以看到Order这个类中,用一个字符串来代表订单客户,但随着开发进度,很可能到了后期你需要为这个客户增加客户地址,信用等级等,这个时候你就不应该用字符串来表示客户,取而代之你应该使用对象。一开始使用Order这个类的客户端代码可能是这样

static int numberOfOrdersFor(QList<Order> orders, QString customer)
{
int result = ; foreach (Order order, orders)
{
if (order.customer() == customer)
{
result++;
}
} return result;
}

首先我们新建Customer类用来表示我们需要替换的新类,然后在这个新类中增加一个const字段用来表示源类中替换字段,这里表示客户姓名name这个概念,然后为这个字段增加取值函数和构造函数。

class Customer
{
public:
Customer(const QString &name) :
m_name(name)
{
} QString name() const
{
return m_name;
}
private:
const QString m_name;
};

接下来我们就需要修改源类了,首先我们将替换字段的类型从QString替换为我们所定义的类型Customer。然后修改所有引用该字段的函数,让他们改而去引用Customer这个对象,其中取值函数和构造函数的修改比较简单,设值函数我们让他重新创建一个新的Customer对象来跟之前的语义(字符串)达成一致形成值对象而非引用对象的概念

class Order
{
public:
Order(const QString &customer)
{
m_customer = new Customer(customer);
} QString customer() const
{
return m_customer.name();
} void setCustomer(const QString &value)
{
delete m_customer;
m_customer = NULL; m_customer = new Customer(value);
}
private:
Customer m_customer;
};

这也就意味着每个Order有属于自己的Customer,注意这样一条规则:值对象是不可以修改内容的。这便可以让你避免一些别名问题。如果你日后想让Customer成为引用对象(reference object)那就是另外一个重构手法了,现在我们进行编译并且测试。

同时需要观察Order类中m_customer字段的操作函数,并作出一些修改使它更好的反应修改后的形式。对于取值函数你可以使用Rename Method让它更清晰的表示自己,因为他返回的是消费者名称,因为此时的消费者已经是一个确确实实的类了。

QString customerName() const
{
return m_customer.name();
}

至于构造函数和设值函数,就不需要修改签名了,但你可以改变他们的参数名称。

Order(const QString &customerName)
{
m_customer = new Customer(customerName);
} void setCustomer(const QString &customerName)
{
delete m_customer;
m_customer = NULL; m_customer = new Customer(customerName);
}

当然,后续的重构也许会添加接受现有的Customer对象作为参数的构造函数和设值函数。

本次重构到此为止,但这个案例和其他案例一样,只是一个中间步骤,还需要后期处理。因为从上文我们可以看到目前我们对待Customer这个类是采用值的概念来对待。如果要给这些Customer增加地址和信用等级我们做不到,因为多个Order存储的Customer不是共同引用同一个对象,所以我们必须使用Change Value to Reference,这样一来,同一个客户的所有Order就可以共享一个Customer对象达到我们所要的效果了。

『重构--改善既有代码的设计』读书笔记----Replace Data Value with Object的更多相关文章

  1. 『重构--改善既有代码的设计』读书笔记----Replace Method with Method Object

    有时候,当你遇到一个大型函数,里面的临时变量和参数多的让你觉得根本无法进行Extract Method.重构中也大力的推荐短小函数的好处,它所带来的解释性,复用性让你收益无穷.但如果你遇到上种情况,你 ...

  2. 『重构--改善既有代码的设计』读书笔记----Replace Array with Object

    如果你有一个数组,其中的元素各自代表不同东西,比如你有一个 QList<QString> strList; 其中strList[0]代表选手姓名,strList[1]代表选手家庭住址,很显 ...

  3. 『重构--改善既有代码的设计』读书笔记----Replace Temp with Query

    Replace Temp with Query,顾名思义,表示你用查询来替换临时变量本身,临时变量对于函数来说是只有当前函数可见的,如果你在同类的别的地方要用到这个变量你就必须重新写表达式来获取这个变 ...

  4. 『重构--改善既有代码的设计』读书笔记----Change Value to Reference

    有时候你会认为某个对象应该是去全局唯一的,这就是引用(Reference)的概念.它代表当你在某个地点对他进行修改之后,那么所有共享他的对象都应该在再次访问他的时候得到相应的修改.而不会像值对象(Va ...

  5. 『重构--改善既有代码的设计』读书笔记----Extract Method

    在编程中,比较忌讳的一件事情就是长函数.因为长函数代表了你这段代码不能很好的复用以及内部可能出现很多别的地方的重复代码,而且这段长函数内部的处理逻辑你也不能很好的看清楚.因此,今天重构第一个手法就是处 ...

  6. 『重构--改善既有代码的设计』读书笔记----Introduce Explaning Variable

    有时候你会遇到一系列复杂的表达式连续运算的时候,这个时候你可能根本招架不住如此长或者是如此复杂的长函数.这个时候你可以通过引用临时变量来储存他们的结果,将这些长函数的结果分成一个个临时变量来让函数清晰 ...

  7. 『重构--改善既有代码的设计』读书笔记---Duplicate Observed Data

    当MVC出现的时候,极大的推动了Model与View分离的潮流.然而对于一些已存在的老系统或者没有维护好的系统,你都会看到当前存在大把的巨大类----将Model,View,Controller都写在 ...

  8. 『重构--改善既有代码的设计』读书笔记----Self Encapsulate Field

    如果你直接访问一个字段,你就会和这个字段直接的耦合关系变得笨拙.也就是说当这个字段权限更改,或者名称更改之后你的客户端代码都需要做相应的改变,此时你可以为这个字段建立设值和取值函数并且只以这些函数来访 ...

  9. 『重构--改善既有代码的设计』读书笔记----Move Method

    明确函数所在类的位置是很重要的.这样可以避免你的类与别的类有太多耦合.也会让你的类的内聚性变得更加牢固,让你的整个系统变得更加整洁.简单来说,如果在你的程序中,某个类的函数在使用的过程中,更多的是在和 ...

随机推荐

  1. 数据结构之数组Array

    数组Array 基本操作 Status InitArray(int dimm,...)//若维数dim和随后的各维长度合法,则构造相应的数组A,并返回OK Status DestroyArray() ...

  2. sql2005中如何启用SA账号

    如下图

  3. Asterisk 安装与配置

    如果用来管理 1.4 版本的 Asterisk ,可能会存在未知的问题.通过集成 CentOS . Asterisk 和 FreePBX , Fonality 公司提供了一个完全傻瓜式的 Asteri ...

  4. dede 如何去除[field:title/]里面出现的b标签

    调用[field:title/]标签,两边加<b>怎么去掉<b> 怎么回事??? 最近更新dede的版本后,调用[field:title/]标签,生成的标题两边会自动在标题两边 ...

  5. appium api

    AppiumDriver getAppStrings()      默认系统语言对应的Strings.xml文件内的数据.iOS driver.getAppStrings(Stringlanguage ...

  6. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(38)-Easyui-accordion+tree漂亮的菜单导航

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(38)-Easyui-accordion+tree漂亮的菜单导航 系列目录 本节主要知识点是easyui ...

  7. Linux编程---线程

    首先说一下线程的概念.事实上就是运行在进程的上下文环境中的一个运行流.普通进程仅仅有一条运行流,可是线程提供了多种运行的路径并行的局面. 同一时候,线程还分为核心级线程和用户级线程.主要差别在属于核内 ...

  8. 通过WriteProcessMemory改写进程的内存

    http://www.cnblogs.com/feiyucq/archive/2009/10/21/1587628.html 以PROCESS_ALL_ACCESS权限打开进程以后既能够使用ReadP ...

  9. redundant 行记录格式

    CREATE TABLE `mytest2` ( `t1` varchar() DEFAULT NULL, `t2` varchar() DEFAULT NULL, `t3` ) DEFAULT NU ...

  10. json-lib-2.4-jdk15.jar maven

    最近自己将一个web项目装换到使用mevan自动管理. 遇到了一个json包导入的问题.最终解决如下: <!-- https://mvnrepository.com/artifact/net.s ...