关闭

Symbian OS 中的Class命名约定(M类)

622人阅读 评论(0) 收藏 举报

计算机界的民间传说在谈到“mix-ins”的起源时,普遍认为这一概念最早源于Symbolic's Flavors——一个早期的面向对象编程系统。 它的设计者显然从“史蒂夫的冰淇淋客厅”(麻省理工学院的学生们特别喜欢的一个冰淇店)中获得了灵感,顾客们在那里选择不同冰淇淋的风味(香草,草莓,巧 克力等等)然后再加入一些混合物(坚果, 奶油软糖, 巧克力碎屑等等)。

   当谈到多重继承时,这就意味着在基础的“风味”类中加入“mix-in”类来扩展它的功能。我相信这就是Symbian的设计者使用mixin这一术语并且把M设为前缀的原因,尽管在Symbian OS中的多重继承和mixin的使用要比冰淇淋店中要复杂得多。

    总体而言,M类是一个抽象接口类。一个具体类(concrete class)通常把基础的“风味”类(例如CBase或CBase派生类)作为它的第一 个基类,然后再加入一个或一个以上的M类“mixin”接口来实现接口函数。在Symbian OS中,M类通常定义为回调(callback)接口或监 听器(observer)接口。
一个M类可以由其它M类继承得来,下面我给出了两个例子。第一个展示了一个实现接口函数的具体类派生至CBase 和一个mixin类——MDomesticAnimal,MdomesticAnimal类派生于MAnimal类但并没有对它进行实现,实际上,它是 MAnimal接口的一个特化(specialization)。

class MAnimal
{
public:
  virtual void EatL() =0;
};
class MDomesticAnimal : public MAnimal
{
public:
  virtual void NameL() =0;
};
class CCat : public CBase,public MDomesticAnimal
{
public:
  virtual void EatL(); // 通过MDomesticAnimal继承自MAnimal 
  virtual void NameL(); // 继承自MDomesticAnimal
…………………………
};

   第二个示例展示了CBase的派生类和两个mixin接口,MRadio和MClock。在这种情况下,MClock并不是MRadio的特化,CClockRadio这个具体类分别继承并实现了这两个接口。对于M类继承而言,这种复杂组合的多重继承是可以实现的。
class MRadio
{
public:
   virtual void TuneL() =0;
};
class MClock
{
public:
   virtual void CurrentTimeL(TTime& aTime) =0;
};
class CClockRadio : public CBase, public MRadio, public MClock
{
public:
   virtual void TuneL();
   virtual void CurrentTimeL(TTime& aTime);
…………………………
};

    上面展示了多重接口继承的使用,这也是在Symbian OS中唯一鼓励使用的一种多重继承形式。其它形式的多重继承将会产生相当复杂的继承分级,并且标 准基类也没有考虑这方面的设计问题。因而,对两个CBase派生类进行多重继承将会在编译时引起二义性问题,只能通过虚拟继承来解决这一问题:

class CClass1 : public CBase
{…………………………};
class CClass2 : public CBase
{…………………………};
class CDerived : public CClass1,public CClass2
{…………………………};
void TestMultipleInheritance()
{
// 无法编译, CDerived::new 有二义性
// 应明确定义CBase::new继承自CClass1还是CClass2?
   CDerived* derived = new (ELeave) CDerived();
}

   让我们来考虑一下M类的特性,它可能被认为等价于java语言中的接口。和java接口一样,M类没有数据成员,所以M类对象不需要进行初始化,因而也就不必为它编写构造函数。
    
    自然而然的,你也会谨慎的考虑M类是否应该有析构函数(无论是虚的还是非虚的)。M类的析构函数给它的接口实现方式增加了限制条件,使得它必须在 CBase的派生类中实现。这是因为析构函数就意味着调用delete,因而就要求对象必须基于heap而不能基于stack建立。这就是说实现类一定派 生于CBase,就是因为这一原因,所有的T类和绝大部分的R类都不存在析构函数。

   在通过一个指向接口类的指针拥有一个M类对象时,需要提供一些销毁M类对象的方法。如果你确信能够保证你的接口类的实施类一定派生于CBase类,那你可以在M类中提供一个虚析构函数。这样就可以让M类的所有者通过调用delete来删除它。

    如果不定义虚析构函数,对一个M类指针进行delete操作将会引发USER 42 panic,这是因为:派生于M类的具体类必然也派生于其它类,如 CBase或CBase的派生类,mixin指针应在继承声明次序中排在C类之后[1],并且这个M类的指针会转而指向M类的子对象,而不是指向有效主对 象heap单元的起始位置,这个M类的子对象是用于访问分配到heap单元上的对象的。

   可以使用内存管理代码来释放heap空间,User::Free(),但如果待清除单元的定位错误将会引发panic。这一问题可以通过使用虚析构函数来解决。

   但是,定义M类接口是并不是惟一的解决方案,你也可以考虑定义一个派生于CBase的抽象基类来代替M类。因为CBase恰好已经提供了一个虚析

1 具体类定义格式为
class CCat : public CBase, public MDomesticAnimal{…};
而 不是class CCat : public MDomesticAnimal, public CBase{…};“风味”C类必须在基类列表的第一 位,以此强调继承树的次序。类似这样的C类对象也可以通过重载CleanupStack::PushL()来压入清洁栈(参见第3章)
构函数,并且接口类的实施类也仅限于在C类中实现,所以这也是一个理想的办法。当然,在这一方法中,实施类将仅限于单重继承(single inheritance),就象在上面所说的那样,对CBase的多重继承将会引起二义性和可怕的“菱形”继承结构。

    总体而言,一个mixin接口类不应关心ownership的实现细节。如果调用者有可能通过指向M类接口的指针拥有一个对象,正如上面所说的那样,你就 必须确保提供一个在M类的所有者在不再需要M类时删除它的方法。但是,这并不限于通过析构函数进行清除。你也可以提供一个纯虚Release()函数来代 替析构函数,然后类的所有者就可以说“搞定了”——由实现代码决定使用何种清除方式(例如一个C类,就可以调用“delete this”来完成)。这是 一个更灵活的接口——实施类基于stack或heap实现都可以,它可以实现引用计数,专门的清除或其它等等的功能。顺便说一句,它实质上并不能调用清除 函数Release()或Close(),但它可以帮助你的调用者。首先,它是可识别的并足以让你推测出它将要干什么。更重要的是,它能够让用户使用 CleanupReleasePushL()函数(参见第3章)。
类似于java的接口,一个M类通常仅有纯虚函数。但是,也有例外允许使用非纯 虚函数。比如说:当某个接口的所有继承类都具有通用的缺省行为时,给这个接口添加共享的实现就可以减少代码的冗余和维护等令人头痛的事情。当然,这个缺省 的通用实现功能有很多限制,因为mixin类不能有数据成员。所以通常情况下,所有的虚函数都是通过调用mixin接口的纯虚函数实现功能的。 
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1540796次
    • 积分:14341
    • 等级:
    • 排名:第876名
    • 原创:166篇
    • 转载:381篇
    • 译文:16篇
    • 评论:146条
    文章分类