Object Oriented Programming (3)

4759 ワード

このセクションでは、Effective C++by Scott MeyersおよびUMLオブジェクト向け設計ベースby Meilir Page-Jonesから選択します.
 
3多態3.1の概要簡単に言えば、マルチステート(polymorphism)は、複数の形態を表す能力を有する特徴であり、開発言語は、オブジェクトのタイプに応じて異なる方法で処理する能力を有する.マルチステートは、多くのクラスが同じ属性または方法を提供することができることを意味し、呼び出し者は、これらの属性または方法を呼び出す前に、あるオブジェクトがどのようなタイプであるかを知る必要はない.3.2仮想関数は、継承の概念を解いた後、もう少し深く入ると2つの種類に分かれていることがわかります.関数インタフェース(function interface)の継承と関数実装(function implementations)の継承.仮想関数(virtual function)は「インタフェースは継承されなければならない」を意味し、純粋な仮想関数(pure virtual function)は「インタフェースのみが継承されなければならない」を意味し、「非仮想関数」は「インタフェースも実装も継承されなければならない」.一般的な(非純粋な)仮想関数と純粋な仮想関数の場合、サブクラスは関数のインタフェースを継承しますが、異なるのは、一般的な仮想関数は伝統的に実装コードを提供し、サブクラスは書き換えるかどうかを選択することができます(override).(純粋でない)仮想関数の目的は、この関数のインタフェースとデフォルトの動作をサブクラスに継承させることです.ただし、一般的な仮想関数では、関数宣言とデフォルトの動作を同時に指定できます.ああ、危険になることもあるので、XYZ航空が設計した継承システムを考えてみると、同社は2つの飛行機しかなく、A型とB型で、どちらも同じ方法で飛行しているので、継承システムはこのように設計されています.
class Airpost{…}; //   
class Airplane
{
public:
    virtual void fly(const Airpost& destination);
    …
}

void Airplane::fly(const Airport& destination)
{
    default code for flying
}

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

すべての飛行機が飛ぶことができることを示すために、「異なる型番の飛行機は原則的に異なるfly実装コードが必要だ」と考慮し、Airplane::flyはvirtualと宣言された.しかし、ModelAとModelBで同じコードを書くことを避けるために、デフォルトの飛行動作はAirplane::flyによって提供され、ModelAとModelBによって継承されます.この設計は共通の性質を際立たせ、コードの重複を回避し、将来的に強化する能力を提供し、長期的なメンテナンスを遅らせるために必要な支払いを遅らせる--これらは正式に対象技術に人気がある理由です.XYZ社の利益が大幅に増加したと仮定し、新しいC型機を購入することにした.C型機はA型とB型とは異なり、より正確には飛行方式が異なる.MXYZの開発者は継承システムの中でC型飛行機にclassを追加したが、彼らは急いで新しい飛行機をオンラインサービスさせたため、fly関数を再定義するのを忘れてしまいました.これは、ModelAまたはModelBの飛行でModelCを飛ばそうとするソフトウェアのためです.問題は、Airplane::flyが提供するデフォルトの動作ではなく、ModelCが「デフォルトの動作を使用します」と理解していない場合にデフォルトの動作を継承していることです.次のような解決策があります.
class Airplane
{
public:
    virtual void fly(const Airpost& destination) = 0;
    …
protected:
    void defaultFly(const Airport& destnation);
}

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

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

class ModelC : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    …
};
void ModelC::fly(const Airport& destination)
{
    code for flying ModelC
}

開発者は、コードをクリップしてModelCを実現することでトラブルを招く可能性がありますが、元の設計よりも信頼できます.Airplane::defaultFlyは仮想関数ではないことが重要です.この関数を再定義するサブクラスはありません.上記のflyとdefaultFlyのように、インタフェースとデフォルトの動作を異なる関数でそれぞれ提供することに反対する人もいます.これによりclass内部のネーミングスペース(naming space)が汚染されるためです.しかし、インタフェースとデフォルトの動作が分離されるべきであることにも同意しています.では、この表面上の矛盾はどのように解決されますか.C++では、純粋な仮想関数の定義(defination)を提供できますが、それを呼び出す唯一の方法は、いわゆる静的呼び出しです.たとえば、
class Shape
{
public:
    virtual void draw() const = 0;
    virtual void error(const string& msg);
    int objectId() const;
}
class Rectangle : public Shape {…};
class Ellipse : public Shape {…};

Shape *ps = new Rectange;
ps1->Shape::draw();

XYZ社に戻ると、以下の新しい設計はほとんど以前と同じですが、純粋な仮想関数Airplane::flyの実装はメンバー関数Airplane::defaultFlyに取って代わりました.これは「純粋な仮想関数はサブクラスで再宣言(declare)しなければならないが、純粋な仮想関数も独自の定義(defination)を持つことができる」という事実を利用している.しかし、この設計にはAirplane::flyの宣言と定義に異なる保護レベルを持たせる機会を失うという問題もある.
class Airplane
{
public:
    virtual void fly(const Airpost& destination) = 0;
    …
}

void Airplane::fly(const Airport& destination)
{
    //default code for 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:: (destination);} 
    …
};

class ModelC : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    …
};
void ModelC::fly(const Airport& destination)
{
    //code for flying ModelC
}

以上の知識があって、メンバー関数(member function)を宣言する場合は、すべてのメンバー関数を非仮想関数として宣言しないように慎重に選択する必要があります.これにより、サブクラスに特殊化作業を行うのに十分なスペースがありません.非仮想的な構造関数(destructors)は特に問題をもたらします.また、すべてのメンバー関数を仮想関数として宣言しないでください.(Javaはどのようにしているか考えてみてください?)は、継承システムにとって変化しない動作があるため、「不変性」を確保したい場合は、非仮想関数を使用します.