Edmund's zone

导航

接口继承和实现继承

设计类(class)的时候,你可能会干下面这几件事情:

1.只让继承类(derived class)继承成员函数的接口;

2.让derived class同时继承函数的接口和实现,但又能覆写所继承的实现;

3.同时继承函数的接口和实现,但不允许覆写任何东西;

以下面的类为例,来解释上面的三种实现:

class Shape
{
    public:
        virtual void draw() const = 0;
        virtual void error(const std::string& msg);
        int objectID() const;
        ...
};

class Rectangle: public Shape{...};
class Ellipse: public Shape{...};

对于draw函数,其实我们是可以调用的:如下:

Shape *ps = new Shpae;               //Error! Shape is abstract
Shape *ps1 = new Rectangle;         //ok
ps1->draw();                         //call Rectangle::draw
Shape *ps2 = new Ellipse;            //ok
ps2->draw();                         //Ellipse::draw
ps1->Shape::draw();                  //Shape::draw
ps2->Shpae::draw();                  //Shape::draw

对于Shape::error这个函数,为非纯虚函数,他告诉继承类,你如果不想自己写一个,则会调用基类的缺省版本。

这种允许非纯虚函数的双面性行为,可能造成危险。例如,如果有个航空公司,该公司有A型和B型两种飞机,两者以相同的方式飞行,设计继承体系如下:

class Airport    {...};
class Airplane
{
   public:
        virtual void fly(const Airport &destination);
        ...    
};    

void Airplane::fly(const Airport &destination)
{
 //fly to the destination.   
}

class ModelA: public Airplane {...};
class ModelB: public Airplane {...};

现在,假设该公司购买了一种新式飞机C,飞行模式有些不同,该公司的程序员在继承体系中针对这种飞机添加了一个class,假如他们忘记了定义其fly函数:

class ModelC: public Airplane
{
   ...//not implementing fly() 
};

调用的时候,你会采用如下的操作:

Airport BCIA(...);        //Beijing Capital International Airport;
Airplane *pa = new ModelC;
...
pa->fly(BCIA);            //call Airplane::fly

上面的程序试图以ModelA和ModelB的飞行方式来飞ModelC,用喷气式飞机的方式来飞螺旋桨飞机,要出人命啊。

问题不在于Airplance::fly有缺省行为,而在于ModelC在未说明“我要”的情况下就继承了该缺省行为。

我们可以避免这种做法:

class Airplane
{
   public:
   virtual void fly(const Airport &destination) = 0;
   ...
   protected:
   void defaultFly(const Airport &destination);  
};
void Airplane::defaultFly(const Airport &destination)
{
 //implementation of flying.   
}

将Airplane::fly改为一个pure virtual函数,这样的话,每个继承类都要自己实现fly:

class ModelA: public Airplane
{
   public:
   virtual void fly(const Airport &destination)
   {
       defaultFly(destination);
   }
   ...  
};

class ModelB: public Airplane
{
   public:
   virtual void fly(const Airport &destination)
   {
      defaultFly(destination);
   }    
   ...
};    

现在ModelC必须自己实现fly函数来,嘿嘿:

class ModelC: public Airplane
{
   virtual void fly(const Airport &destination);
   ... 
};

void ModelC::fly(const Airport &destination)
{
  //ModelC's way.
}

这种方式可能因为使用者的复制粘贴而错误,但是比之前有了保障。至于Airplane::defaultFly,现在设置成了protected,因为他是继承类的实现方式,用户只在意飞机能不能飞,不在意怎么飞的。

当然,现在这种方式也有个问题,多定义了一个defaultFly,可能会导致命名空间污染的问题。我们可以用下面这种方式,就是纯虚函数必须在继承类中重新声明,但是他们可以拥有自己的实现。例如:

class Airplane
{
    public:
        virtual void fly(const Airport &destination) = 0;
};
void Airplane::fly(const Airport &destination)
{
    //do flying;
}

class ModelA: public Airplane
{
    public:
        virtual void fly(const Airport &destination)
        {
            Airplane::fly(destination);
        }
        ...
};

class ModelB: public Airplane
{
    public:
        virtual void fly(const Airport &destination)
        {
            Airplane::fly(destination);
            ...
         }
};

class ModelC: public Airplane
{
    public:
        virtual void fly(const Airport &destination);
        ...
};
void ModelC::fly(const Airport &destination)
{
   //do ModelC's flying;
}    

最后,对于objectID这个非虚函数,意味着在继承类中,不需要对其进行修改。所以在继承类中不建议重新定义。这个会在下个议题中阐述。

  

posted on 2015-04-21 15:45  Edmund Li  阅读(261)  评论(0编辑  收藏  举报