Lab 14-2: Proper Inheritance

 
實習目標 課堂中舉了一個 Shape-Point-Circle-Cylinder 的不適當的繼承範例, 請綜合運用繼承, 組合的語法來設計較好的程式架構
 
步驟一 不適當的繼承範例如下:
    class Shape
    {
    public:
        virtual float area() const { return 0.0; }
        virtual float volume() const { return 0.0; }
    };
    
    class Point: public Shape
    {
    public:
        Point(float x=0, float y=0, float z=0) 
        { this->x = x; this->y = y; this->z = z; }
        float getX() const { return x; }
        float getY() const { return y; }
        float getZ() const { return z; }
    private:
        float x, y, z;
    };
    
    class Circle: public Point
    {
    public:
        Circle(float r=0., float x=0., float y=0., float z=0.)
            :Point(x,y,z),radius(r){}
        float area() const { return 3.141159*radius*radius; }
        float getRadius() const { return radius; }
    private:
        float radius;
    };
    
    class Cylinder: public Circle
    {
    public:
        Cylinder(float h=0., float r=0., 
            float x=0., float y=0., float z=0.):
            Circle(r,x,y,z), height(h) {}
        float area() const 
        { return 2*Circle::area()+height*2*3.14159*getRadius(); }
        float volume() const { return Circle::area()*height; }
    private:
        float height;
    };
    
    void main()
    {
        Point point1(1,2,3), point2(4,5,6);
        Circle circle(5, 2,0,-2);
        Cylinder cylinder1(5, 3, 0,0,0), cylinder2(4, 2, 1,1,1);
        Shape *shapes[] = {&point1, &point2, &circle, 
                           &cylinder1, &cylinder2};
        
        int i;
        for (i=0; i<5; i++)
        {
            shapes[i]->display(cout);
            cout << endl;
            cout << "   area:" << shapes[i]->area();
            cout << "   volume:" << shapes[i]->volume() << endl;
        }
    }
上面程式碼中類別的階層如下圖:
這樣子的類別階層設計, 最主要的錯誤在於子類別的物件和父類別的物件並沒有 IS-A 的關係, 也就是沒有取代性 (substitutibility), 在簡單的測試程式中並不見得會造成什麼錯誤, 但是將來如果想要擴充類別設計時, 或是要整合使用這些類別的物件時就很容易發生錯誤。
步驟二 我們仔細審查所要製作的這幾個類別: Point, Circle, Cylinder, 希望重新組合這幾個物件的設計, 運用比較多的重用機制來避免程式碼的重複, 但是又不至於錯用繼承的機制。

我們發現 Circle 物件中應該可以用到一個 Point 物件來表達圓心所在, Cylinder 物件中應該可以用兩個 Circle 物件來表達上底及下底兩個圓。 另外如果希望在程式中以一致的方法來處理, 儲存這些圖形物件的話, 我們可以將共通的資料與界面抽出來成為 Shape 物件, 如下圖所示:

步驟三 請修改類別的定義來完成上圖的架構

首先 Shape 類別的界面除了保留 area() 及 volume() 之外, 再加上 display(), 為了透過多型指標來操作 Shape 衍生類別的物件, 這些界面函式都宣告為虛擬函式 (virtual function), 同時因為這個類別為抽象的共同界面, 所以這些虛擬函式也定義為純粹的虛擬函式 (pure virtual function):

    class Shape
    {
    public:
        virtual float area() const=0; 
        virtual float volume() const=0;
        virtual void display(ostream &os)=0;
    };
雖然 area() 及 volume() 是純粹虛擬函式, 在 Shape.cpp 中我們還是可以實作 Shape::area() 以及 Shape::volume() 來容納共通的程式, 例如:
    float Shape::area() const 
    { 
        return 0.0;
    }
如果衍生類別需要的話也可以呼叫這個實作。
步驟四 其次我們實作 Point, Circle, 和 Cylinder 三個類別, 每一個類別中都需要實作 area(), volume() 和 display() 三個函式才能夠作出該類別的實體物件。

各個類別運用其它類別來重用程式碼, 並且使用委託的機制來取代步驟一中繼承的程式界面。 例如: Circle 中定義 Point 物件 m_center, Circle 的建構元函式和 display() 函式如下:

    Circle::Circle(float r, float x, float y, float z)
        :m_center(x,y,z), m_radius(r)
    {
    }
    
    void Circle::display(ostream &os)
    {
        os << "Circle: radius=" << m_radius << " center:";
        m_center.display(os);
    }
步驟五 請用下面簡單的程式碼來測試相關的功能:
    #include <iostream>
    using namespace std;
    
    #include "Point.h"
    #include "Circle.h"
    #include "Cylinder.h"
    
    void main()
    {
        Point point1(1,2,3), point2(4,5,6);
        Circle circle(5, 2,0,-2);
        Cylinder cylinder1(5, 3, 0,0,0), cylinder2(4, 2, 1,1,1);
        Shape *shapes[] = {&point1, &point2, &circle, &cylinder1, &cylinder2};
        
        int i;
        for (i=0; i<5; i++)
        {
            shapes[i]->display(cout);
            cout << endl;
            cout << "   area:" << shapes[i]->area();
            cout << "   volume:" << shapes[i]->volume() << endl;
        }
    }
範例執行程式

執行結果範例:

Point: [1,2,3]
   area:0   volume:0
Point: [4,5,6]
   area:0   volume:0
Circle: radius=5 center:Point: [2,0,-2]
   area:78.529   volume:0
Cylinder: height=5 topCircle:Circle: radius=3 center:Point: [0,0,0] 
                   bottomCircle:Circle: radius=3 center:Point: [0,0,5]
   area:150.789   volume:141.352
Cylinder: height=4 topCircle:Circle: radius=2 center:Point: [1,1,1] 
                   bottomCircle:Circle: radius=2 center:Point: [1,1,5]
   area:75.3947   volume:50.2585
步驟六 請助教檢查後, 將所完成的 project (去掉 debug/ 資料匣下的所有內容) 壓縮起來, 選擇 Lab14-2 上傳, 後面的實習課程可能需要使用這裡所完成的程式

C++ 物件導向程式設計課程 首頁

製作日期: 05/31/2004 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615
海洋大學 電機資訊學院 資訊工程系 Lagoon