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
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
FooBaseto support functionality that is ultimately outside of
FooBaseitself doesn’t care about matching at all).
FooBaseotherwise has nothing to do with
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
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
FooBasehaving a dependency on
Bar, you could implement the interface in
FooBso that only they have a dependency on
Bar(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
Put the dependency in
Very similar to
IEnumerable.GetEnumerator, implement a
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.
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
Bar “native” public API and their
IMatcher public API is manipulated in
FooBarMatch to make the match-making happen.