World of iOs

2B or not 2B, it's a problem.

多重继承和接口

--------------------------------------------------------------
多重继承是C++的强大特性之一,这篇文章解释它在Symbian C++平台上的使用。

概览
------------------------------------------------------------
多重继承的经验表明他的好处,最好通过小心控制它在系统中使用的一些简单易懂的范例的方式来实现。
没有那样的控制而使用多重继承将导致难以理解的设计。

在SYmbian平台多重继承被使用达到一个简单的目的:即,接口协议的定义。
这会在下面的情况下使用:有一个协议提供者类,和一个协议用户。
需要做到的是使协议用户独立于协议提供者的所有方面,除了其提供指定协议的能力。
这个情况的例子包括:
1.一个应用程序控制是一个协议提供者;他的菜单树使用菜单观察协议。当一个菜单被选择,菜单观察者协议将被请求

,这样应用控制可以处理菜单命令。如果没有这个协议,菜单控制将对应用控制一无所知。
2.一个应用程序,如数据表,可能有一个提供更新和设置其模型内容的协议的引擎,用户接口使用这些协议来驱动引擎


引擎以不知道用户接口的方式编写,用户接口也对引擎知道得最少。他们相互间使用引擎提供的协议作用。

为了理解为什么使用接口,这一页依次检查:
1.传统的使用简单接口的方法
2.克服简单接口缺点使用协议中介类的技术
3.更好的技术,对接口类使用多重继承
4.SYMBIAN平台C++多重继承的局限

使用类简单接口的协议
------------------------------------------------------------------
一个使用简单接口的类定义一个抽象协议,其派生子类应该继承,一个基类定义了一个接口:
 class CProtocol : public CBase
    {
public:
    virtual void HandleEvent(TInt aEventCode)=0;
    };

协议只包含一个函数,HandleEvent(),事件以整数事件码定义。

一个实际协议提供者类派生自此基类。它提供基类中这个纯虚函数的一个具体实现:
class CProtocolProvider : public CProtocol
    {
public:
    // construct/destruct
    static CProtocolProvider* NewLC();
    void Destruct();
    // implement the protocol
    void HandleEvent(TInt aEventCode); // handle protocol
protected:
    void ConstructL();
    };

而且,有一个协议用户类对CProtocolProvider类一无所知,到那时他知道CProtocol类和指定协议的函数,
他有一个使用HandleEvent()的函数:
      void CProtocolUser::DoSomething(CProtocol* aProtocol)
    {
    _LIT(KOutput1,"External system doing something/n");
    _LIT(KOutput2,"invoking protocol - event 3/n");
    testConsole.Printf(KOutput1);
    testConsole.Printf(KOutput2);
    aProtocol->HandleEvent(3); // handle an event
    }
这个定义在CProtocol中的函数由CProtocol提供,这是实际执行的虚函数:
       void CProtocolProvider::HandleEvent(TInt aEventCode)
    { // handle an event in the protocol user
    _LIT(KOutput1,"CProtocolProvider handling event %d/n");
    testConsole.Printf(KOutput1,aEventCode);
    }
这样,虽然协议用户对派生类CProtocolProvider一无所知,它却可以通过指向派生类的指针请求调用其成员函数,
使用C++虚函数调用机制。

代码可以这样使用:
  void doExampleL()
    {
    // show use of interface with simple class
    CProtocolProvider* provider=CProtocolProvider::NewLC();
    CProtocolUser* user=CProtocolUser::NewLC();
    user->DoSomething(provider);
    CleanupStack::PopAndDestroy(); // user
    CleanupStack::PopAndDestroy(); // provider
    }
在函数调用中,provider指针被转换为其基类Cprotocol*,CProtocolUser::DoSomething()需要他。

这个方法的优点:
1.它获得协议用户和协议提供者间的独立性

这是我们所要达到的目的,但这个方法有严重的缺点:
1.它强迫协议提供者必须派生协议基类
2.如果协议提供者类有多于一个的协议要被提供,唯一的情形是将所有这些协议包含到一个唯一的协议伞下,
而从那个类派生提供者类。这是糟糕的封装。或者哪个函数属于哪个协议。
其次,可能有另一个提供者类希望提供此协议的一部分,且提供其他协议。要支持这种情况,就需要更大的协议伞。

单继承提供协议的直接方法经常导致过大的基类,表示了许多相互应该独立的协议。

使用中间类的协议
---------------------------------------------------------------------------------------
这样一些缺点可以通过使用一个代表协议的中间对象来克服,有一个指向协议提供者的指针。
基类其实是一样的:
class TProtocol
{
public:
virtual void HandleEvent(TInt aEventCode) = 0;
}


但是现在有一个提供CProtocolProvider的派生类:
class TProtocolProviderIntermediary : public TProtocol
{
public:
TProtocolProviderIntermediary(CProtocolProvider* aRealProvider);
void HandleEvent(TInt aEventCode);
private:
CProtocolProvider* iRealProvider;
}
这个类对于协议用户来说提供协议。HandleEvent()的具体实现仅仅传递函数调用到真正的协议调用者类,那个类有一

个非虚函数DoHandleEvent()来提供功能:
void TProtocolProviderIntermediary::HandleEvent(TInt aEventCode)
{
iRealProvider->DoHandleEvent(aEventCode);
}
用这个系统,CProtocolProvider被派生,不是从协议定义类,而是CBase:
  class CProtocolProvider : public CBase
    {
public:
    // construct/destruct
    static CProtocolProvider* NewLC();
    void Destruct();
    // implement the protocol
    void DoHandleEvent(TInt aEventCode); // handle protocol
protected:
    void ConstructL();
public:
    TProtocolProviderIntermediary* iProviderIntermediary;
    };
TProtocolProviderIntermediary由CProtocolProvider构造,且由其析构函数销毁。因为这一点,

TProtocolProviderIntermediary是一个T类:它不拥有CProtocolProvider,并不能被孤立。

当一个请求协议提供者的协议用户函数被调用,它不能将中间对象作为参数传递:
          LOCAL_C void doExampleL()
    {
    // show use of interface with simple class
    CProtocolProvider* provider=CProtocolProvider::NewLC();
    CProtocolUser* user=CProtocolUser::NewLC();
    user->DoSomething(provider->iProviderIntermediary);
    CleanupStack::PopAndDestroy(); // user
    CleanupStack::PopAndDestroy(); // provider
    }
协议用户的DoSomething()函数实际上和之前相同,除了参数现在为TProtocol*。
这样用户只知道基类TProtocol。虚函数机制导致派生中间类的HandleEvent()被调用,
这个函数传递请求到真实协议提供者的DoHandleEvent()中。

这个方法解决了和单继承相关的问题。

任何数量的协议都可被支持,并分别被封装,被一个特定的类:每个协议都需要一个中间类,每个中间类的对象指向真

正的协议提供者类。

不需要过大的类来提供多个协议的伞

但是,它也有严重的问题:

他是笨拙的 :不仅每个协议都需要一个抽象类(不可避免),并且,派生树上的每一个引入协议的点上,一个为真正

提供协议的相关类来实现协议的派生类必须被编写:且派生协议对象和真实协议提供者必须链接在一起。

如果有很多类以这种方式使用很多协议,这个方法不仅麻烦,并且内存消耗也不经济,因为每个派生协议类至少在堆上

需要两个机器字。如果存在更多的小型的真实协议提供者,这种考虑将变得更严重。

协议使用接口类
-----------------------------------------------------
这个问题能被使用多重继承克服。一个MProtocol的基类指定协议:
   class MProtocol
    {
public:
    virtual void HandleEvent(TInt aEventCode)=0;
    };
这一次,协议提供者同时派生自CBase和MProtocol:
 class CProtocolProvider : public CBase, public MProtocol
    {
public:
    // construct/destruct
    static CProtocolProvider* NewLC();
    void Destruct();
    // implement the protocol
    void HandleEvent(TInt aEventCode); // handle protocol
protected:
    void ConstructL();
    };
协议提供这类提供协议所需的HandleEvent()函数的实际实现。用户类可以这样被请求:
 LOCAL_C void doExampleL()
    {
    // show use of interface with simple class
    CProtocolProvider* provider=CProtocolProvider::NewLC();
    CProtocolUser* user=CProtocolUser::NewLC();
    user->DoSomething(provider);
    CleanupStack::PopAndDestroy(); // user
    CleanupStack::PopAndDestroy(); // provider
    }
DoSomething()函数需要一个MProtocol*参数。C++将CProtocolProvider* 提供者的指针向下转为一个MProtocol*,
因为MProtocol是CProtocolProvider的一个基类。当DoSomething()请求HandleEvent()时,C++虚函数机制确保

CProtocolProvider的HandleEvent()被实际调用。这样,用户可以使用协议,而对实际协议提供者类一无所知。

这个方法达到了以下目的:

1.协议用户依赖于协议,但不依赖任何特定的提供者
2.协议能被引入到任何需要点的一个类体系中,以从一个基类和一个或多个接口类的多重继承方式
3.达到了对不同协议的封装
4.没有了麻烦的中间类,他会导致编程困难和内存使用浪费

因为协议可以混合到常规类集成体系中任何方便的点,有时将这些协议规格类成为混入类,即M前缀的由来。

多继承使用上的限制
--------------------------------------------------------
多继承的使用如上面的描述是仅限于接口的。C++的完全多继承工具有不必要的复杂性。
这现在可能已经被00社区意识到,如JAVA,只允许单继承,但是接口和实现关键字支持类似于M类方式。
下面详细给出限制的具体描述。

首先,M类主要用于定义协议,而不是实现。尤其是,他们不能有任何成员数据。这个限制暗示某些类型的行为(如,

活动对象),不能被封装到接口中,而要以传统的方式派生。

其次,一个C类可以从另一个C类及零到多个M类继承,这个限制反映了多重继承仅仅被用于接口。同时也暗示仍然可能

唯一地识别一个主继承树(C类体系),而接口作为辅助特性。如果任意的多继承被允许,是有可能识别出一个主继承

树的。限制同时也担保了C类不会成为一个多重基类,从而导致多基类继承中不必要的复杂物 -- virtual inheritance

第三,C类必须为任何基类列表的第一个指定类。这点强调了主继承关系树,重要的是,这让C类(在接口中包含他们)

和void*指针件的自由转换提供便利成为可能。不能否认,C++标准中没有规定对象的布局要遵循基类的指定顺序,但是

这符合多数编译器的情况,包含哪些symbian平台上使用的。

第四,M类不可以在任何类中混入多次,不论是其直接基类还是其之前基类中的任何一个。换句话说:当一个C类CD派生

自基类CB,你不应该再混入CB已经混入的M类MP。这说明事实上CB已经支持MP定义的协议:再从这个协议类获取不到任

何东西。而且,又要考据多基类继承中不必要的麻烦 -- virtual inheritance 等。

最后,虽然从另一个M类派生一个M类是合法的,但从一个协议或其派生协议两次包含同一个协议到C类,在这个C类的基

类图中任何一点都是非法的。换句话说,有一个MD派生自MB,C类不能既包含MB又包含MD,因为C累土工对MB协议的实现

将同MD协议的实现相冲突。

用法示例
------------------------------------------------------------------
回调

接口的一个特例是回调。这种情况下,一个类为另一个类提供某个函数,调用请求类中的某个函数,表明请求操作已完

成。回调函数表示一个协议:请求类是提供者,执行类是用户。除了这些,执行类不需要知道这个请求类,这是接口的

一个理想状态。

双向使用

目前为止,我们讨论了一个类按照指定协议提供服务,另一个类使用其服务。
更一般的情况,两个类(或类系统)可能需要相互请求服务,所以存在双向动作。

服务总是依协议而提供。提供协议用到的技术在此文档中描述:

1.传统继承,适合于协议特征是一个类的主要目的
2.接口继承,适用于协议可能是多个类的某个共同特征,但是这些类有不同的用途
3.中间对象,适用于一个接口可能被使用,但当多继承被允许,或者由于牟宗原因不方便

观察者

GUI程序使用菜单表示选择项的用户接口。当一个选项被选择,菜单条将向前传递一个命令道某处,通过调用某个类的

一个成员函数。对菜单条唯一重要的事情是,有某个对象能够处理这个命令:除此以外,不关心此对象。
       class CEikMenuBar ...
    {
public:
    ConstructL(MEikMenuObserver* aObserver, ...);
    // ...
private:
    MEikMenuObserver* iObserver;
    // ...
    }
一个菜单条因此使用一个菜单观察者,这是传入构造函数的一个参数,保存为成员数据,当一个选项被选择时使用。
菜单观察者接口由菜单组件定义,就像MEikMenuObserver类一样。

这个接口由APP UI实现(同时也做许多其他事情,而菜单不关心)。所以,CEikAppUi实现MEikMenuObserver的菜单观

察者接口:
 class CEikAppUi : public CCoeAppUi, MEikMenuObserver
APP UI有一个菜单条,当它构造这个菜单条,APP UI将自己作为观察者传给菜单条:
iMenuBar->ConstructL(this,...);
C++将this自动转换成合适的基类 -- 在这里是 MEikMenuObserver。

阅读更多
个人分类: Symbian General
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭