接口繼承和實現繼承

設計類(class)的時候,你可能會幹下面這幾件事情:程序員

1.只讓繼承類(derived class)繼承成員函數的接口;api

2.讓derived class同時繼承函數的接口和實現,但又能覆寫所繼承的實現;函數

3.同時繼承函數的接口和實現,但不容許覆寫任何東西;spa

如下面的類爲例,來解釋上面的三種實現:設計

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函數,其實咱們是能夠調用的:以下:code

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這個函數,爲非純虛函數,他告訴繼承類,你若是不想本身寫一個,則會調用基類的缺省版本。blog

這種容許非純虛函數的雙面性行爲,可能形成危險。例如,若是有個航空公司,該公司有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() 
};

調用的時候,你會採用以下的操做:ip

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這個非虛函數,意味着在繼承類中,不須要對其進行修改。因此在繼承類中不建議從新定義。這個會在下個議題中闡述。

相關文章
相關標籤/搜索