委托模式(转自:http://www.uml.org.cn/j2ee/200411036.htm)

 

委托模式

透明 选择自 www.c-view.org

 

梗概

委托是对一个类的功能进行扩展和复用的方法。它的做法是:写一个附加的类提供附加的功能,并使用原来的类的实例提供原有的功能。

场景

扩展和复用一个类的功能常用的一种方法是继承,而另一种更普遍的方法则是委托。在很多情况下委托很适用,而继承则并不适用。另外在[MEYERS98]中也讲到,公有继承表现的设计思想是“is-a-kind-of” ,私有继承表现的设计思想则是“is-implemented-in-terms-of”[1],这些关系都是静态的、不能在运行时改变的。而在一些情况下我们需要表现的设计思想是“is-a-role-played-by”的关系,在这些情况下不应该用继承的方法。

下面用一个例子来帮助说明。假设我们为一个航空公司设计软件系统,于是我们必须用一些类来表示各种各样的“人”,包括机组人员、售票员、旅客等等。一种思路是这样:因为这些人都是抽象的“人”,因此设计一个Person抽象类,并从这个抽象类衍生出我们需要的各种类。由此我们得到下面的类图:


图1 用继承的方法建模

这个设计方案的问题是很明显的:一个机组人员在休假的时候可能乘坐飞机而成为一个旅客;航空公司也可能把机组人员调去做售票员……是的,一个人可能成为三种角色中的任何一种。如果我们一定要坚持继承的思路,那么我们可能得到下面这个图:(见图2)


图2 继承解决方案的发展(类爆炸的实例)

很明显我们遇到了“类爆炸”的问题。我们这里只有三个角色,就需要用七个衍生类来表现所有的情况。如果我们有六个角色呢?我们将需要63个衍生类。(我画图2用了15分钟的时间,如果要画63个衍生类……呵呵,呵呵)

而且即使使用了这么多衍生类,我们仍然有困难。因为继承所表现的“is-a-kind-of”关系是静态的,在编译时就固定了。而一个“人”可能在不同的时间扮演不同的角色,于是我们可能需要用多个对象来表现同一个“人”的不同角色,这也是一件相当麻烦的事情。

而另一方面,如果我们用委托的方式来表现这个问题,我们可以得到一个相当优雅的解决方案,上面提到的问题都自然的解决了。这样的解决方案如图3所示:


图3 使用委托的建模

约束

如果你发现一个对象需要在不同的时间“成为”不同的衍生类,那么首先这个对象根本不应该“是”一个衍生类。因为一个对象一旦作为衍生类被创建出来,它就只能是这个衍生类的实例而不能扮演其他角色了。另一方面,一个对象可以在不同的时间把不同的行为委托给不同的对象。

如果你发现一个衍生类在试图隐藏其超类的方法或变量,这说明这个类根本不应当从这个超类衍生得到,因为根本没有什么合理的理由来隐藏超类的方法或变量。但另一方面,如果使用委托的设计方法,你就可以随意选择需要的方法或变量。

把一个类设计成现有的具体类的衍生类也不是一件值得推荐的事情。(这个话题的C++版本在[MEYERS96]中有非常详细的介绍,因此我就不在这里赘言了。)

“不适当的继承”在实际中被如此广泛的应用,以至于可以把它们归纳成一种“反模式(antipattern)”了。正如上面所说的,继承一个具体类可能导致各种无法预料的问题。实际上,可能绝大多数对类的功能的扩展和复用都不应该使用继承。

解决方案

委托是对类的行为进行复用和扩展的一条途径。它的工作方式是:包含原有类的实例引用,实现原有类的接口,将对原有类方法的调用转发给内部的实例引用。图4展示了本模式的一般形式:


图4 使用委托模式对类的行为进行复用和扩展

委托的用途比继承更加广泛。用继承能实现的对类的任何形式的扩展都可以用委托的方式完成。因此在[GoF]中也建议尽量用委托代替继承。

参与者

  • Delegator(委托者)
    - 保存Delegate的实例引用。
    - 实现Delegate的接口。
    - 将对Delegate接口方法的调用转发给Delegate。
  • Delegate(受委托者)
    - 接受Delegator的调用,帮助Delegator实现其接口。

效果

使用委托模式可以避免继承方法遇到的问题。另外,使用委托模式可以很容易的在运行时对其行为进行组合。

委托模式的主要缺点是类之间的联系、类体系结构不如继承那样清楚明显。不过也有一些方法可以改善这些联系的清楚程度。

  • 使用一致并且清楚的名称,让程序的读者可以直观的知道Delegator和Delegate之间的联系。比如说,如果用一个类来代理一些Widget衍生类的创建,那么把这个类命名为WidgetFactory就是不错的方法。
  • 在程序中写上适当的注释,告诉读者:这里使用委托模式。
  • 遵循Law of Demeter模式[GRAND99],即:如果两个类之间只有间接联系,采用间接委托;如果有直接联系,采用直接委托。这可以减少类之间的联系数量。
  • 使用大家都知道的设计和编码模式。因为委托模式是很多其他模式的基础,如果在其他模式中使用委托模式,读者更容易理解。

实现

委托模式的实现是非常直接的。Delegator保存Delegate的实例引用,并转发相应的方法调用。

C++应用

委托模式可以说是无处不在的,C++中的例子附拾皆是。一个例子就是标准库中的auto_ptr。auto_ptr用一个私有变量保存原始指针,并将对operator*和operator&的调用转发给原始指针。(在[MEYERS96]中有对auto_ptr的详细描述。)

代码示例

作为实际的例子,我们仍然考虑“场景”一节中讲的航空公司软件系统。这一次我们要在“飞行段” [2]中随时检查飞机上行李的数量。我们用FlightSegment类表示飞行段,用LuggageCompartment表示行李舱,Client将使用checkLuggage()函数向FlightSegment查询行李情况。一个飞行段可能会使用不同的飞机,当然也就有不同的行李舱了。我们使用委托模式实现对行李的检查,得到如图5所示的类图:


图5 “行李检查”的例子中使用的类

如图所示,任意时刻一个FlightSegment都保存一个LuggageCompartment的指针(当飞行进行的时候,这个指针不为NULL;没有飞行的时候,这个指针则为NULL),它把checkLuggage()函数调用转发给LuggageCompartment,并将返回值传回给Client。

首先是FlightSegment的代码:

class LuggageCompartment;                                                             //前置声明,保证成员指针正确声明

class FlightSegment

{

private:

    LuggageCompartment * m_pLC;                             //成员指针

public:

    void SetLuggageCompartment(LuggageCompartment * pLC)

                         { m_pLC = pLC; }                               //设置成员指针

    FlightSegment()                                                                            //构造函数将成员指针初始化为null

                         { m_pLC = 0 };

    int checkLuggage()

    {                 

                         if(m_pLC==0)

                                             return -1;

                         return m_pLC->checkLuggage();         //将函数调用委托给成员指针

    }

};

然后是LuggageCompartment的代码:

class LuggageCompartment

{

private:

    int m_iLuggage;                                                                            //私有变量,保存现在的行李总数

public:

    LuggageCompartment();                                           //构造函数

                         { m_iLuggage = 0; }

    int TakeoutLuggage();                                              //取出一件行李

    {

                         if(m_iLuggage!=0)

                                             m_iLuggage--;

                         return m_iLuggage;

    }

    int InsertLuggage();                                                  //放入一件行李

                         { return (++m_iLuggage); }

    int checkLuggage();                                                  //检查行李总数

                         { return m_iLuggage; }

};

最后是client可能写出这样的代码:

    ...

#include

 

    ...

    FlightSegment segment;

    LuggageCompartment lc1, lc2;

    ...

    for(int i=0; i<10; i++)

                         lc1.InsertLuggage();

    ...

    segment.SetLuggageCompartment(&lc1);

    cout << "Now we have " << segment.checkLuggage()

         << " Luggages." << endl;

    ...

    segment.SetLuggageCompartment(&lc2);

    cout << "Now we have " << segment.checkLuggage()

             << " Luggages." << endl;

    ...

(以上程序在MICROSOFT VC++6.0及BORLAND C++ BUILDER 5.0编译通过。)

相关模式

几乎所有其他模式都使用了本模式。一些模式——例如Decorator模式和Proxy模式[GRAND98]──对Delegation模式的依赖是最为明显的。

参考书目

[GoF] Gamma etc., Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley 1995.
中文版:《设计模式:可复用面向对象软件的基础》,李英军等译,机械工业出版社2000年。

[GRAND98] Mark Grand, Patterns in Java (volume 1), Wiley computer publishing 1998.

[GRAND99] Mark Grand, Patterns in Java (volume 2), Wiley computer publishing 1999.

[MEYERS96] Scott Meyers, More Effective C++, Addison-Wesley 1996.
繁体中文版:《More Effective C++国际中文版》,侯捷译,培生教育出版集团2000年。

[MEYERS98] Scott Meyers, Effective C++ (2nd Edition), Addison-Wesley 1998.
繁体中文版:《Effective C++ (2nd Edition) 国际中文版》,侯捷译,培生教育出版集团2000年。

注释

[1] [MEYERS98]的ITEM35指出,public继承表示“is-a”的关系,也就是这里所说的“is-a-kind-of”。

[2] “飞行段”是指旅行中的一部分。一个飞行段中,飞机将从一个机场起飞、在另一个机场停留、然后再次起飞,旅客不必换飞机。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值