これについて少し考えてみましょう。サブクラスが 2 つしかないと確信しているので、これを一般化しましょう。
最初に頭に浮かぶのは、コードの重複、拡張性、近さです。これらを拡張しましょう:
さらにクラスを追加したい場合は、可能な限り最小限の場所でコードを変更する必要があります。
intersect
操作は可換であるため、交差するコードと交差するコードは、交差するコードA
とB
同じ場所にある必要があるため、クラス自体の内部にロジックを保持することは問題外です。B
A
また、新しいクラスを追加しても、既存のクラスを変更する必要はありませんが、デリゲート クラスを拡張する必要があります (はい、ここでパターンを説明します)。
これはあなたの現在の構造だと思います(または同様の、おそらく の戻り値の型ですintersect
が、今のところ重要ではありません):
struct Primitive
{
virtual void intersect(Primitive* other) = 0;
};
struct Sphere : Primitive
{
virtual void intersect(Primitive* other)
};
struct Plane : Primitive
{
virtual void intersect(Primitive* other);
};
Plane
or内に交差ロジックを入れたくないことはすでに決まっているSphere
ので、新しい を作成しますclass
。
struct Intersect
{
static void intersect(const Sphere&, const Plane&);
//this again with the parameters inversed, which just takes this
static void intersect(const Sphere&, const Sphere&);
static void intersect(const Plane&, const Plane&);
};
これは、新しい関数と新しいロジックを追加するクラスです。たとえば、Line
クラスを追加する場合は、メソッドを追加するだけintersec(const Line&,...)
です。
新しいクラスを追加するとき、既存のコードを変更したくないことを思い出してください。そのため、交差関数内の型を確認できません。
この (戦略パターン) の動作クラスを作成できます。これは、タイプに応じて異なる動作をし、後で拡張できます。
struct IntersectBehavior
{
Primitive* object;
virtual void doIntersect(Primitive* other) = 0;
};
struct SphereIntersectBehavior : IntersectBehavior
{
virtual void doIntersect(Primitive* other)
{
//we already know object is a Sphere
Sphere& obj1 = (Sphere&)*object;
if ( dynamic_cast<Sphere*>(other) )
return Intersect::intersect(obj1, (Sphere&) *other);
if ( dynamic_cast<Plane*>(other) )
return Intersect::intersect(obj1, (Plane&) *other);
//finally, if no conditions were met, call intersect on other
return other->intersect(object);
}
};
そして、元のメソッドでは、次のようになります。
struct Sphere : Primitive
{
virtual void intersect(Primitive* other)
{
SphereIntersectBehavior intersectBehavior;
return intersectBehavior.doIntersect(other);
}
};
さらにクリーンな設計は、ファクトリを実装して、動作の実際のタイプを抽象化することです。
struct Sphere : Primitive
{
virtual void intersect(Primitive* other)
{
IntersectBehavior* intersectBehavior = BehaviorFactory::getBehavior(this);
return intersectBehavior.doIntersect(other);
}
};
intersect
すべてのクラスに対してこれを行うだけなので、仮想である必要さえありません。
このデザインに従うと
- 新しいクラスを追加するときに既存のコードを変更する必要はありません
- 実装を 1 か所に配置する
IntersectBehavior
新しいタイプごとにのみ拡張
Intersect
新しい型のクラスに実装を提供する
そして、これはさらに完成される可能性があるに違いありません。