Introduce Foreign Method一样,很多时候你不能修改编辑原始类,你需要为这些服务类增加一些额外的函数,但你没有这个权限或者入口。如果你只需要一个或者两个外加函数那么你可以放心的使用Introduce Foregin Method,但是如果你发现此时有很多外加函数需要在客户类代码中中添加,你就要小心了,因为你这么做你就会让客户类变得过分复杂,责任就会过分多,你会破坏客户类的单一职责性。这个时候你就可以建立一个新类,让他来包含这些你之前所添加的额外函数,让这个扩展类成为源类的子类或者包装类

在这里提供了两种标准对象技术--子类化(subclass)和包装(wrapping),在这两种方法下,作者统一称他们为本地扩展(Local Extension)。所谓本地扩展就是一个独立的类,但也是被扩展类的子类型,他可以提供源类的一切特性同时可以添加新特性,在任何使用源类的地方你都可以使用本地扩展取而代之。

坚持使用本地扩展可以让你坚守“函数和数据应该被统一封装”的原则。如果你一直把本该放在扩展类的代码零散地放置于其他类中,最终你会让这些客户类变得过分复杂,让其中的函数变得难以复用。

在子类和包装类之间做选择时,作者推荐优先使用子类,这也是有道理的,因为子类可以让你最少的去修改或者去添加源类本身有的特性,包装类你需要做一系列简单委托来维持原特性。但是子类化也是有比较麻烦的地方,就是它必须在对象创建期实施,如果你可以接管对象的创建期那当然没问题。但如果你想在对象创建之后再进行本地扩展那就有问题了。此外你还需要去面对别名问题,因为子类化对象通常是新创建一个子类对象,在这种情况下如果还有其他类引用了旧对象,你就同时有了两个对象保存了相同的原始数据。如果原数据是不可修改的,那还好没有什么问题,但如果是可修改的,你的问题就来了,因为一个修改不可能同时修改两个副本,如果你需要面对这个问题,那么你就应该使用包装类,因为只有使用包装你才可以让本地扩展(Local Extension)波及原对象,反之如果你不想让本地使用波及原来的数据,那么你应该优先使用子类化的方案。

  • 做法:
  • 建立一个扩展类,由你选择判断让他作为源类的子类或者包装类。
  • 在扩展类中加入“转型构造函数”,所谓“转型构造函数”就是指“接受原对象作为参数”的构造函数。如果采用子类化方案,那么转型构造函数应该调用适当的超类构造函数,如果采用包装类方案,那么转型构造函数应该将它得到的传入参数以实例变量的形式保存起来,用以接受委托对象。注意:使用转型构造函数更多是为了隐藏扩展类,让用户从使用上,特别是针对函数参数是源类的时候,可以做到转型构造。
  • 在扩展类中加入新特性。
  • 根据需要将原对象替换为扩展对象。
  • 将针对原始类增加的所有外加函数搬移到扩展类中。

例子:

class MfDateSub : public Date
{
public:
MfDateSub *nextDay() const...
int dayOfYear()...
}; class MfDateWrap
{
private:
Date *m_date;
};

针对我们上一篇的例子,我们有这两种方式来引入本地扩展,一个就是子类化,一个就是包装类。我们先来看看子类化

class MfDateSub : public Date
{
public:
MfDateSub(const QString &dateString) :
Date(dateString)
{
}
};

我们可以把基类构造函数的参数传入给基类的构造函数来完成扩展类的构造,现在我们需要同时引入一个转型构造函数,参数就是源类对象。

class MfDateSub : public Date
{
public:
MfDateSub(const QString &dateString) :
Date(dateString)
{
}
MfDateSub(Date *date) :
Date(date->getTime())
{
}
};

现在我可以在扩展类中增加新特性,用Move Method将之前添加的额外函数nextDate()移动到这个扩展类

clien class...
static Date *nextDay(Date *arg)
{
// foreign method
return new Date(arg->getYear(), arg->getMonth(), arg->getDate() + );
} class MfDateSub...
Date *nextDate()
{
return new Date(getYear(), getMonth(), getDate() + );
}

我们再来看下使用包装类的过程

class MfDateWrap
{
public:
MfDateWrap(const QString &dateString)
{
m_date = new Date(dateString);
}
private:
Date *m_date;
};

包装类的构造函数和之前有所不同,现在的构造函数只是很简单的执行一个委托动作。而转型构造函数则只是对它的字段赋值而已。

MfDateWrap(Date *arg)
{
m_date = arg;
}

接下来就是一连串枯燥的工作,我们要为原始类的所有函数提供委托函数。

int getYar()
{
return m_date->getYear();
} int getMonth()
{
return m_date->getMonth();
}

完成这个工作之后我们进行Move Method把额外函数引入到扩展类

class MfDateWrap...
Date *nextDate()
{
return new Date(getYear(), getMonth(), getDate() + );
}

当然,使用包装类有一个问题就是,就是如何处理接受原始类作为参数的函数,比如

bool after(Date *arg):

由于我们无法改变原始类,我们只能在一个方向上做到兼容,即在包装类的after()函数可以接受包装类或者原始类对象,但原始类的after()函数只能接收原始类对象,不能接受包装类对象。

wrapper->after(date); // ok
wrapper->after(anotherWrapper); // ok
date->after(wrapper); // error

所以其实我们应该显示的给包装类增加一个额外的after函数进行覆写(子类化不需要,因为子类指针可以被基类指针的参数所接受)即

bool after(Wrapper *arg):

这样覆写的目的就是为了向用户隐藏包装类的存在,这是一个好策略,因为包装类的客户的确不应该知道包装类的存在,的确应该可以同样的对待原始类和包装类,但是我们无法完全隐藏包装类的存在,原因就是上文所说的,源类可能不能通过包装类的参数来完成函数的调用,特别是对于equals()这种满足交换率的来说更是如此。因此我们只能做出妥协,创建一个新的函数来单独完成针对两种类的比较

bool equalsDate(Date *arg);
bool equalsDate(MfDateWrap *arg);

这样你就不必检查未知对象的类型了。当然了子类化方案中没有这样的问题,只要我不覆写原函数就行了。但如果你覆写了原始类中的函数,那么当你寻找函数时就容易晕头转向,一般来说,我们不会在扩展类中覆写原始函数,只会添加新函数。

『重构--改善既有代码的设计』读书笔记----Introduce Local Extension的更多相关文章

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

    当你无法获得一个类的源代码或者没有权限去修改这个类的时候,你对于这种为你服务的类,你可能会出现需要别的需求的时候,比如一个Date类,你需要能够让他本身直接返回出他的后一天的对象,但他没有,这个时候你 ...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. lightoj1057 - Collecting Gold (tsp问题)

    题目链接:http://lightoj.com/volume_showproblem.php?problem=1057 题目大意:在二维矩阵中,给你一个起点和至多15个的目标点.要你求出从起点出发经过 ...

  2. TOYS - POJ 2318(计算几何,叉积判断)

    题目大意:给你一个矩形的左上角和右下角的坐标,然后这个矩形有 N 个隔板分割成 N+1 个区域,下面有 M 组坐标,求出来每个区域包含的坐标数.   分析:做的第一道计算几何题目....使用叉积判断方 ...

  3. visual studio 添加链接文件

    本文转载http://blog.163.com/zhongpenghua@yeah/blog/static/87727415201282432345613/   那个有个箭头的文件就是链接文件了,添加 ...

  4. Android中RelativeLayout的字符水平(垂直居中)对齐

    [背景] 此处Android中显示出来的TextView中的内容,水平中间不对其. 想要实现水平居中对齐. [折腾过程] 1.搜: android RelativeLayout horizontal ...

  5. Android 动画之RotateAnimation应用详解

    android中提供了4中动画: AlphaAnimation 透明度动画效果 ScaleAnimation 缩放动画效果 TranslateAnimation 位移动画效果 RotateAnimat ...

  6. iOS IAP教程

    1. 创建应用 首先进入iTunes Connect然后按下 Manage Your Applications 接下来按下Add New Applicationbutton创建应用 2. 在应用中创建 ...

  7. otg线是什么,otg线和数据线的区别介绍

    OTG是什么? OTG主要应用于各种不同的设备或移动设备间的联接,进行数据交换.USB技术的发展,使得PC和周边设备能够通过简单方式.适度的制造成本将各种数 据传输速度的设备连接在一起.上述我们的应用 ...

  8. ReentrantLock与synchronized的差别

    总的来说,lock更加灵活. 主要同样点:Lock能完毕synchronized所实现的全部功能 不同: 1.ReentrantLock功能性方面更全面,比方时间锁等候,可中断锁等候,锁投票等,因此更 ...

  9. mapreduce实战:统计美国各个气象站30年来的平均气温项目分析

    气象数据集 我们要写一个气象数据挖掘的程序.气象数据是通过分布在美国各地区的很多气象传感器每隔一小时进行收集,这些数据是半结构化数据且是按照记录方式存储的,因此非常适合使用 MapReduce 程序来 ...

  10. 设置textView或者label的行间距方法

    一,效果图. 二,代码. RootViewController.m - (void)viewDidLoad { [super viewDidLoad]; // Do any additional se ...