2

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

  1. Introduces a dependency on Bar to FooBase to support functionality that is ultimately outside of FooBase (FooBase itself doesn't care about matching at all). FooBase otherwise has nothing to do with Bar.

  2. 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
  • What language is this in? (In some languages, a standalone polymorphic function could be used without being a part of FooBase)
    – Telastyn
    Commented Jun 6, 2016 at 17:53
  • @Telastyn - It's C# (I just added a tag)
    – Andrew
    Commented Jun 6, 2016 at 17:59
  • Then depending on the visibility of the elements used to do the match, an extension method may better decouple things.
    – Telastyn
    Commented Jun 6, 2016 at 18:02
  • 1
    Can you provide a more concrete, real-world example? Your foobar example doesn't really provide any insight into why you've made these particular design choices. Commented Jun 6, 2016 at 19:12
  • Without knowing how the IsMatch method works (at least, roughly), it is impossible to say if it is a good or bad choice to place it in the Foo hierarchy. Could it easily be implemented outside of FooA and FooB in terms of their public methods/properties, or does it need access to internals of those objects? Please clarify.
    – Doc Brown
    Commented Jun 6, 2016 at 19:16

2 Answers 2

1

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 on Bar, you could implement the interface in FooA and FooB so 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 foo and bar

1

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.