5

Here's the original problem. There are two classes: Protocol and, say, ProtocolUser (which actully uses the Protocol class instance). Now, I need to support multiple protocols, whose feature sets only partly intersect, doing similar but not equal things. I grouped the features into several interfaces. That is:

interface IA {}
interface IB {}
interface IC {}

interface IP {};
class P_AB : IP, IA, IB {}
class P_AC : IP, IA, IC {}
class P_BC : IP, IB,IC {}

class P_User
{
    IP m_p;

    ??? // needs to use m_p via IA, IB or IC, depending on which are supported
}

And the most vexed part: the protocols are to be switched on-the-fly. Methods of P_User will have to choose which interfaces to operate on, depending on which will be supported by the call time.

The initial plan was to rely on is & as operators. But it does look suspiciously cumbersome. The only alternative I see is to invent some kind of "CanDoThis / CanDoThat" infrastructure, but that doesn't differ much from a plain is.

Are there any helpful patterns I'm unaware of?

4
  • "it does look suspiciously cumbersome" In what way?
    – pdr
    May 26, 2015 at 14:00
  • @pdr the 'if else if' train strongly reminds me of the switch-by-type situation, where polymorphism can often fit better. But I don't clearly see it here, either...
    – vines
    May 26, 2015 at 14:08
  • I see why it appears that way, but it's not. As opposed to "if this condition, use this type, else if this condition, use another type, etc" this is "if the current protocol supports X, use it; if it supports Y, use it; if it supports Z, use it." The alternative is to have no-op methods in each protocol class and call them all, which is pretty hard to follow at scale. "Can" methods, as you point out, are no different from is and as.
    – pdr
    May 26, 2015 at 15:19
  • @pdr Yeah, that's exactly why I decided to try anyway :)
    – vines
    May 26, 2015 at 21:47

3 Answers 3

2

You have really two options here:

  • factoring - isolate interfaces of each object and use soft casting (as operator) to determine extended capabilities of the class
  • optional feature pattern - implement all methods available on all classes in one base class and throw NotSupportedException when the given function is not supported.

Both of those have their pros and cons: Factoring:

  • pro: responsibilities/capabilities of your code are well separated
  • con: the code becomes more complex, and functionality hard to discover

Optional features pattern:

  • pro: Functionality is easier to discover using tools like Intellisense
  • con: The base class is bloated with unnecessary functions that might throw

Since you've said that your interfaces should intersect only a little I would go with the factoring approach, it is more suited for such cases. A more realistic example would be needed to give you more detailed answer.

Edit: If some of the interface combinations are expected to be used more than other you might consider combining them in another interface:

public interface IAB : IA, IB

This would of course result in interface explosion if you did it for every possible combination, so use this sparingly only in cases where it really makes sense. It's always easier to add a new interface than to remove one.

4

Dependency Inversion should be best fit in your case.

The interfaces should not be defined based on supported capabilities, but on what P_User is expecting from the protocol. Each protocol can then implemented what P_User needs based on it's capabilities.

5
  • 2
    I think that using the Strategy design pattern would help the OP achieve this.
    – npinti
    May 26, 2015 at 14:31
  • 2
    I think the point here is that P_User doesn't EXPECT anything of m_p, but if things are provided then it uses them. I don't see how DIP solves that problem.
    – pdr
    May 26, 2015 at 15:35
  • Yes, @pdr is right, that's the case. I've considered strategies as well, but the protocol classes often have internal state, and it doesn't play well with a strategy set. It can be done, but unlikely to be simpler...
    – vines
    May 26, 2015 at 23:08
  • @vines How internal state doesn't play well with strategy?
    – Euphoric
    May 27, 2015 at 4:24
  • @Euphoric Virtually any of the strategies would have to be able to switch the state and other parallel strategies in a batch. I hardly see how to do it without weaving too many interdependencies or noticeable marshalling complication. (Also, a set of strategies doesn't just look as a right abstraction for a single protocol, but that's debatable.)
    – vines
    May 27, 2015 at 9:56
3

What you could do is a variation of an Abstract Factory. Something like this:

public class ProtocolFactory
{
    public IP _protocol;

    public ProtocolFactory(IP protocol)
    {
         _protocol = protocol;
    }

    public IA GetAService()
    {
         return _protocol as IA ?? new NoOpProtocol();
    }

    public IB GetBService()
    {
         return _protocol as IB ?? new NoOpProtocol();
    }

    // etc
}

public class NoOpProtocol : IA, IB, IC
{
     // Implement all interfaces as no-ops
}

So now your calling code is

var factory = new ProtocolFactory(m_p);
factory.GetAService().DoStuff();
factory.GetBService().DoMore();
// etc

This seems less cumbersome, keeps the soft-casting functionality in one place, but still allows you extensibility, with as many protocols as you like, each implementing as few or as many interfaces as you like.

2
  • 1
    This is an interesting sort of "reverse" visitor pattern. There is an explicit type check, but it is hidden inside of the factory, and the use of the null object means that no one is the wiser.
    – cbojar
    May 28, 2015 at 0:41
  • Yes, interesting solution. Unfortunately, the actual method usage logic differs so much that it just won't fit into a no-op scheme... Otherwise I'd considered doing it :)
    – vines
    May 29, 2015 at 9:29

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.