鋳造
これはほぼ間違いなく引用された本のアプローチに完全に正直になるでしょうが、ISPによりよく準拠する1つの方法は、QueryInterface
COMスタイルのアプローチを使用して、コードベースの1つの中心的な領域でキャストの考え方を採用することです。
純粋なインターフェースコンテキストで重複するインターフェースを設計する多くの誘惑は、多くの場合、1つの正確な狙撃のような責任を果たすよりも、インターフェースを「自給自足」にしたいという欲求から生じます。
たとえば、次のようなクライアント関数を設計するのは奇妙に思えるかもしれません。
// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `position` and `parenting` parameters should point to the
// same object.
Vec2i abs_position(IPosition* position, IParenting* parenting)
{
const Vec2i xy = position->xy();
auto parent = parenting->parent();
if (parent)
{
// If the entity has a parent, return the sum of the
// parent position and the entity's local position.
return xy + abs_position(dynamic_cast<IPosition*>(parent),
dynamic_cast<IParenting*>(parent));
}
return xy;
}
...これらのインターフェイスを使用してクライアントコードにエラーが発生しやすいキャストを実行する責任を漏らしていること、および/または同じオブジェクトを引数として同じオブジェクトの複数のパラメーターに複数回渡すことを考えると、非常に醜く/危険です関数。多くの場合の懸念統合し、より希釈されたインタフェースを設計するために望んで終わる我々はそうIParenting
とIPosition
、一つの場所でのようにIGuiElement
、または、その後も同様のためのより多くのメンバ関数を持っているように誘惑される直交インタフェースの懸念と重複の影響を受けやすくなり、そのような何か同じ「自給自足」の理由。
責任の混合とキャスティング
完全に蒸留された極めて特異な責任を持つインターフェースを設計する場合、多くの場合、いくつかのダウンキャストを受け入れるか、インターフェースを統合して複数の責任を果たします(したがって、ISPとSRPの両方を踏襲します)。
COMスタイルのアプローチ(QueryInterface
一部のみ)を使用することで、ダウンキャストアプローチを採用しながら、コードベースの1つの中心的な場所にキャストを統合し、次のようなことを行うことができます。
// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `obj` should implement `IPosition` and optionally `IParenting`.
Vec2i abs_position(Object* obj)
{
// `Object::query_interface` returns nullptr if the interface is
// not provided by the entity. `Object` is an abstract base class
// inherited by all entities using this interface query system.
IPosition* position = obj->query_interface<IPosition>();
assert(position && "obj does not implement IPosition!");
const Vec2i xy = position->xy();
IParenting* parenting = obj->query_interface<IParenting>();
if (parenting && parenting->parent()->query_interface<IPosition>())
{
// If the entity implements IParenting and has a parent,
// return the sum of the parent position and the entity's
// local position.
return xy + abs_position(parenting->parent());
}
return xy;
}
...もちろん、うまくいけば、タイプセーフなラッパーと、生のポインタよりも安全なものを取得するために一元的に構築できるすべてのものがあります。
これにより、多くの場合、重複するインターフェースを設計するという誘惑が最小限に抑えられます。ISPを気にすることなく、好きなだけ組み合わせることができ、C ++での実行時の疑似ダックタイピングの柔軟性を得ることができます(もちろん、オブジェクトが特定のインターフェイスをサポートするかどうかを確認するためにクエリオブジェクトを実行する際のランタイムペナルティのトレードオフ)。ランタイム部分は、たとえば、これらのインターフェースを実装するプラグインのコンパイル時情報を事前に関数が持たないソフトウェア開発キットの設定で重要になる場合があります。
テンプレート
テンプレートが可能である場合(オブジェクトを取得するまでに失われない、必要なコンパイル時の情報が事前にある場合)、次のように簡単に実行できます。
// Returns the absolute position of an entity as the sum
// of its own position and the position of its ancestors.
// `obj` should have `position` and `parent` methods.
template <class Entity>
Vec2i abs_position(Entity& obj)
{
const Vec2i xy = obj.xy();
if (obj.parent())
{
// If the entity has a parent, return the sum of the parent
// position and the entity's local position.
return xy + abs_position(obj.parent());
}
return xy;
}
...もちろん、そのような場合、parent
メソッドは同じEntity
型を返す必要があります。その場合は、インターフェイスを完全に回避する必要があります(ベースポインタを操作するために型情報を失うことがよくあるため)。
エンティティコンポーネントシステム
柔軟性またはパフォーマンスの観点からCOMスタイルのアプローチをさらに追求し始めると、多くの場合、業界でゲームエンジンが適用されるものと同様のエンティティコンポーネントシステムになります。その時点で、多くのオブジェクト指向のアプローチに完全に垂直になりますが、ECSはGUI設計に適用できる場合があります(シーン指向のフォーカスの外でECSを使用することを考えた場所の1つですが、後で遅すぎると考えましたCOMスタイルのアプローチで解決しようとしています)。
このCOMスタイルのソリューションは、GUIツールキットの設計に関しては完全に存在し、ECSはさらに多くなるため、多くのリソースに支えられるものではないことに注意してください。それでも、責任が絶対的に最小になるインターフェースを設計するという誘惑を和らげることが確実にできるため、多くの場合それが問題になることはありません。
実用的なアプローチ
代替は、当然のことながら、細かいレベルであなたのガードビット、または設計インターフェースをリラックスして、あなたが使用することを粗いインターフェイスを作成するためにそれらを継承し始める、のようなものIPositionPlusParenting
の両方から派生しているIPosition
とIParenting
(うまくいけば、それよりも良い名前で)。純粋なインターフェイスでは、一般的に適用されるモノリシックな深い階層型のアプローチ(Qt、MFCなど)ほどISPに違反してはなりません。ドキュメントでは、違反するISPのレベルが高すぎるため、関係のないメンバーを非表示にする必要があると感じることがよくあります。したがって、実用的なアプローチでは、あちこちでいくつかのオーバーラップを受け入れることができます。しかし、この種のCOMスタイルのアプローチにより、これまでに使用したすべての組み合わせに対して統合されたインターフェイスを作成する必要がなくなります。このような場合、「自給自足」の懸念は完全に排除されます。これにより、SRPとISPの両方と戦いたいと考える責任が重複するインターフェースを設計するという誘惑の最終的な原因がしばしば排除されます。