2

継承ではなく、どのように構成にアプローチする必要がありますか?次のクラスについて考えてみます。

class GameObject {...};

class Sprite {
public:
    void changeImage(...);
};

class VisibleGameObject: public Sprite, public GameObject {};

class VisibleGameObject : public GameObject {
    protected:
        Sprite m_sprite;
};

最初のVisibleGameObjectクラスは継承を使用します。多重継承。よく見えません。2つ目は使用したいものですが、次のようにSpriteのAPIにアクセスできません。

VisibleGameObject man;
man.changeImage();

継承(またはコードの重複)なしでそれをどのように達成できますか?

m_sprite編集:私は継承を使用するか、パブリックメンバーを作成することができ、Spriteクラスにアクセスできないことを知っていますprivateVisibleGameObjectそれがポイントです。問題は、データのカプセル化のルールに従って、スプライトを変更するための最良の方法についてです。

4

3 に答える 3

3

あなたはまだ「継承よりも構成」の考え方を持っている一歩だと思います。基本クラスは、何を合成するかを知っている必要があります。イメージを変更するには、スプライトインスタンスを変更する必要があります。構成されたインスタンスのインターフェイスを提供しないでください。例えば:

class GameObject {
public:
    // you can choose public access or getters and setters, it's your choice
    Sprite sprite;
    PhysicalBody body;
};

object = GameObject();
object.sprite = graphicalSystem.getSpriteFromImage("image.png");
// or if you prefer setters and getters
object.setSprite(sprite);

より一般的には、GameObjectには、基本クラスComponentのインスタンス(または、実装によってはインスタンスへのポインター)が含まれている必要があります。この場合、継承を使用するのは理にかなっています。これは、継承をstd::mapのように1つのストレージに含めることができるためです。例えば:

class Component {
    // ...
};

class Sprite : public Component {
    //...
};

class PhysicalBody : public Component {
    //...
};

class GameObject {
protected:
    std::map<std::string, Component*> components;
    //...
public:
    Component* getComponent(const std::string& name) const;
    void setComponent(const std::string& name, Component* component);
    //...
};

メインループでのコンポーネントの作成とレンダリングには、Systemsを使用します。たとえば、GraphicalSystemは、作成したスプライトのすべてのインスタンスを認識しており、レンダリング中は、一部のGameObjectインスタンスにアタッチされたスプライトのみをレンダリングします。切り離されたコンポーネントはガベージコレクションできます。位置とサイズに関する情報は、GameObjectの一部である場合もあれば、コンポーネントの「物理的」である場合もあります。

それを理解する最良の方法は、独自のプロトタイプを作成するか、既存の実装(ArtemisUnity 3Dなど)を確認することです。詳細については、カウボーイプログラミング:階層を進化させるか、エンティティ/コンポーネントシステムを見つけてくださいを参照してください。

于 2013-03-24T09:13:15.750 に答える
2

まず第一に、構成の代替手段は、両方のモデルがaの関係を持っているため、プライベート継承(パブリック継承ではない)です。

Sprite重要な問題は、パブリックメンバー(例changeImage)をVisibleGameObjectクライアントに公開するにはどうすればよいかということです。私が知っている4つの方法を紹介します。

(プライベート)継承

(多重)継承を避けたいとのことですが、完全を期すために、私的継承に基づく1つの提案を示します。

class VisibleGameObject: private Sprite, public GameObject {
...
};

この場合VisibleGameObject、個人的にはから派生しSpriteます。その場合、前者のユーザーは後者のメンバーにアクセスできなくなります(プライベートメンバーであるかのように)。特に、Spriteのパブリックメンバーと保護されたメンバーはVisibleGameObjectクライアントに隠されています。

継承が公開されていた場合、すべて Spriteの公開および保護されたメンバーはVisibleGameObject、そのクライアントに公開されます。プライベート継承を使用すると、宣言を使用してどのメソッドを公開するかをより細かく制御できます。たとえば、これは以下を公開しSprite::changeImageます:

class VisibleGameObject1: private Sprite, public GameObject {
public:
    using Sprite::changeImage;
...
};

転送方法

以下に示すようVisibleGameObjectに、呼び出しを転送するパブリックメソッドに与えることができます。m_sprite

class VisibleGameObject2: public GameObject {
public:
    void changeImage() {
        m_sprite.changeImage();
    }
private:
    Sprite m_sprite;
...
};

特にカプセル化に関しては、これが最良の設計だと思います。ただし、他の選択肢に関しては、多くの入力が必要になる場合があります。

構造間接参照演算子

単純な古いCでさえ、別の型のインターフェースをそれ自体であるかのように公開する型を提供します:ポインター。

p確かに、それがタイプであると仮定しSprite*ます。次に、構造間接参照演算子を使用して、以下に示すように(で示される)->のメンバーにアクセスできます。Spritep

p->changeImage();

C ++を使用すると、カスタマイズされた構造体間接参照演算子(スマートポインターでよく使用される機能)をクラスに付与できます。この例は次のようになります。

class VisibleGameObject3 : public GameObject {
public:
    Sprite* operator ->() {
        return &m_sprite;
}
private:
    Sprite m_sprite;
...
};

VisibleGameObject v;
v->changeImage();

この方法には便利ですが、多くの欠点があります。

  1. パブリック継承に関しては、このアプローチでは、どのSpriteパブリックメンバーを公開するかを細かく制御することはできません。
  2. これは1つのメンバーに対してのみ機能します(つまり、同じトリックを使用して2つのメンバーのインターフェースを公開することはできません)。
  3. それはインターフェースを台無しにします。確かに、例えばVisualGameObject、メソッドを持っていると考えてdoSomething()ください。次に、オブジェクトでこのメソッドを呼び出すには実行するv必要がありますv.doSomething()が、呼び出すには。changeImage()を使用する必要がありますv->changeImage()。これは紛らわしいです。
  4. VisibleGameInterfaceスマートポインタのように見えます。これは意味的に間違っています!

C++11ラッパーパターン

最後に、SutterのC ++ 11ラッパーパターンがあります(彼のプレゼンテーション、特に9ページの2番目のスライドをご覧ください)。

class VisibleGameObject4 : public GameObject {
private:
    Sprite m_sprite;
public:
    template <typename F>
    auto operator()(F f) -> decltype(f(m_sprite)) {
        return f(m_sprite);
    }
};

クライアントはこれを次のように使用します。

VisibleGameObject4 v4;
v4( [](Sprite& s) { return s.changeImage(); } );

ご覧のとおり、転送メソッドのアプローチと比較すると、これは入力の負担をクラスライターからクラスクライアントに転送します。

于 2013-03-24T22:01:47.463 に答える
0

最初に参照せずに、Spriteの関数に直接アクセスしようとしているようです。これを試して:

man.m_sprite.changeImage() ;

これを行うには、m_spriteとchangeImage()をパブリックにする必要があることに注意してください。それ以外の場合は、パブリックアクセサ関数を使用してプライベートクラスメンバーを操作します。

于 2013-03-22T04:03:41.643 に答える