I have two separate inheritance hierarchies that represent objects that can be “matched” to each other to consitute a FooBarMatch (which contains references to the matched FooBase and Bar partners):
_____________
| FooBarMatch |
|-------------|
---<>|FooBase |<>---
| |Bar | |
| |_____________| |
| |
| |
______|_______ ________|_____
| <<abstract>> | | Bar |
| FooBase | |______________|
|______________|
^
|
--------------------
_______|______ _______|______
| FooA | | FooB |
|______________| |______________|
The actual logic for determining whether a FooBase
is a match for a Bar
depends on what subtype FooBase
ends up being (if FooBase
is a FooA
we need to execute different logic than if it’s a FooB
). But all FooBase
objects can be compared to a Bar
to see if they are a match.
I am currently handling this as follows: by creating an IBarMatchable
interface with an IsMatch(Bar) : bool
method, which is implemented by an abstract method in FooBase
. FooA
and FooB
then contain different implementations for IsMatch(Bar)
. It looks like this:
_____________
| FooBarMatch |
|-------------|
_______________ ---<>|FooBase |<>---
| IBarMatchable | | |Bar | |
|---------------| | |_____________| |
| IsMatch(Bar) | | |
|_______________| | |
^ ______|_______ ________|_____
| | <<abstract>> | | Bar |
---| FooBase | |______________|
|--------------|
|*IsMatch(Bar)*|
|______________|
^
|
--------------------
_______|______ _______|______
| FooA | | FooB |
|--------------| |--------------|
|IsMatch(Bar) | |IsMatch(Bar) |
|______________| |______________|
Now to determine matches we can only concern ourselves with IBarMatchable
. This seems to accomplish what I want, however I am curious if this is “good design” or not– I’m most concerned that it now seems to
-
Introduces a dependency on
Bar
toFooBase
to support functionality that is ultimately outside ofFooBase
(FooBase
itself doesn’t care about matching at all).FooBase
otherwise has nothing to do withBar
. -
Introduces a second “reason to change” (i.e. the way we define a match changes) to
FooBase
— so potentially a SRP violation.
Does this violate SOLID? Is there a better way to design this that does not require FooBase
to depend on Bar
?
9
There are various ways to do this:
- As mentioned in the comments: an extension method could accomplish the same thing
- If you want to continue with the interface approach but are concerned about
FooBase
having a dependency onBar
, you could implement the interface inFooA
andFooB
so that only they have a dependency onBar
(which is an actual dependency). Then you just work on the interface instead of on the base-class.
Either way, a lot of it depends on your actual model. In a case like this, it’s better to mention the actual model rather than use foo
and bar
Put the dependency in FooBarMatch
Very similar to IEnumerable.GetEnumerator
, implement a IMatcher
in Foo
and Bar
that returns a matcher object – with a IMatchable.GetMatcher
method (and others as needed).
IMatcher.MatchMeUP
will take a counterpart object. An IMatcher
object will necessarily need to know intimate details to make matches. The FooBarMatch
object needs to know what, when, how to call IMatcher
API to make matches and do something with the results. Foo
and Bar
don’t even need to be aware of the existence of any other classes.
However the devil is in the details and there will be design decisions as to how the Foo
& Bar
“native” public API and their IMatcher
public API is manipulated in FooBarMatch
to make the match-making happen.