Think about it for a second. I'm sure you have not only 2 basements, so let's summarize this.
The first things that come to mind are code duplication, extensibility, and proximity. . Expand them:
If you want to add a few more classes, you should change the code as little as possible.
Since the intersect
operation is commutative , the code for intersecting A
and B
must be in the same place as the code for intersecting B
and A
, so the logic inside the classes themselves is out of the question.
In addition, adding a new class should not mean that you need to modify existing classes, but rather extend the delegate class (yes, we enter the templates here).
This is your current structure, I guess (or a similar, probably return type for intersect
, but not important yet):
struct Primitive { virtual void intersect(Primitive* other) = 0; }; struct Sphere : Primitive { virtual void intersect(Primitive* other) }; struct Plane : Primitive { virtual void intersect(Primitive* other); };
We have already decided that we donโt need the intersection logic inside Plane
or Sphere
, so we are creating a new class
:
struct Intersect { static void intersect(const Sphere&, const Plane&);
This is the class in which you will add new functions and new logic. For example, if you decide to add the Line
class, you simply add intersec(const Line&,...)
methods intersec(const Line&,...)
.
Remember that when adding a new class, we do not want to change the existing code. Therefore, we cannot check the type inside your intersection functions.
We can create a behavior class for this (strategy template) that behaves differently depending on the type, and we can continue it:
struct IntersectBehavior { Primitive* object; virtual void doIntersect(Primitive* other) = 0; }; struct SphereIntersectBehavior : IntersectBehavior { virtual void doIntersect(Primitive* other) {
And in our original methods we will have:
struct Sphere : Primitive { virtual void intersect(Primitive* other) { SphereIntersectBehavior intersectBehavior; return intersectBehavior.doIntersect(other); } };
An even cleaner design will be implemented by the factory in order to abstract the actual types of behavior:
struct Sphere : Primitive { virtual void intersect(Primitive* other) { IntersectBehavior* intersectBehavior = BehaviorFactory::getBehavior(this); return intersectBehavior.doIntersect(other); } };
and you donโt even need intersect
be virtual, because it just does it for each class.
If you follow this design
- no need to modify existing code when adding new classes
- have implementations in one place
- only
IntersectBehavior
propagated for each new type - provide
intersect
class implementations for new types
And I am sure that this could be further improved.