まず第一に、構成の代替手段は、両方のモデルが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*
ます。次に、構造間接参照演算子を使用して、以下に示すように(で示される)->
のメンバーにアクセスできます。Sprite
p
p->changeImage();
C ++を使用すると、カスタマイズされた構造体間接参照演算子(スマートポインターでよく使用される機能)をクラスに付与できます。この例は次のようになります。
class VisibleGameObject3 : public GameObject {
public:
Sprite* operator ->() {
return &m_sprite;
}
private:
Sprite m_sprite;
...
};
と
VisibleGameObject v;
v->changeImage();
この方法には便利ですが、多くの欠点があります。
- パブリック継承に関しては、このアプローチでは、どの
Sprite
パブリックメンバーを公開するかを細かく制御することはできません。
- これは1つのメンバーに対してのみ機能します(つまり、同じトリックを使用して2つのメンバーのインターフェースを公開することはできません)。
- それはインターフェースを台無しにします。確かに、例えば
VisualGameObject
、メソッドを持っていると考えてdoSomething()
ください。次に、オブジェクトでこのメソッドを呼び出すには実行するv
必要がありますv.doSomething()
が、呼び出すには。changeImage()
を使用する必要がありますv->changeImage()
。これは紛らわしいです。
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(); } );
ご覧のとおり、転送メソッドのアプローチと比較すると、これは入力の負担をクラスライターからクラスクライアントに転送します。