2

Imagine, I have class IAlgo which is an interface. I have derived from him and implemented his single method called matchCount in different implementations - AlgoA1, AlgoA2, AlgoA3, AlgoB1, AlgoB2.

class IAlgo
{
    virtual int matchCount(T1* p1, T2* p2) = 0;
}

class AlgoA1 : public IAlgo
{
    virtual int matchCount(T1* p1, T2* p2) override
    {
         // impl here
    }
}

The difference between AlgoAN and AlgoBK is that category A is interested in all params of matchCount and category B is interested in only first param and the rest can be null - category B does not use them.

So as an encapsulated algorithm I am using them in Strategy pattern. After some time, the product owner says that he wants a new type of algorithms (AlgoC category) that adds a new parameter to matchCount method - say T3*. So we should go back and change the whole hierarchy of IAlgo and all subclass to have the following form:

    virtual int matchCount(T1* p1, T2* p2, T3* p3) = 0;

This is a design problem and it seems that I had encapsulated algorithm to use it as a strategy, but it didn't work. How I have to solve this problem? Is my problem is the fact that AlgoA, AlgoB and AlgoC families are not related and they should not derive from IAlgo and they don't have "IS A" relationship? Should I have different interfaces IAlgoA, IAlgoB and IAlgoC for different families? Or all this is fine and I need some other solution?

8
  • 2
    Are the users of IAlgo calling a mixture of AlgoAN and AlgoBK, or is each user only using one family of algorithms? Aug 20, 2015 at 8:23
  • Nice question, thanks! Each user uses one family. Users are divided to families. There is a IUser and here are IUserA, IUserB and IUserC.
    – Narek
    Aug 20, 2015 at 8:27
  • The naming makes it hard to reason about this problem. Could you give a concrete example of what p1, p2, p3 might actually represent? Aug 20, 2015 at 11:56
  • @BenAaronson please look at my other question: programmers.stackexchange.com/questions/294132/…
    – Narek
    Aug 20, 2015 at 12:21
  • @Narek Well that one isn't really much better, because it's no easier for me to translate that analogy into an actual situation I'll face in code. Aug 20, 2015 at 12:28

4 Answers 4

6

As indicated in the comments, there is no mixed usage of algorithms from the different families.

If there is no use-case where an IAlgo can refer to either an AlgoAN or an AlgoBK instance, then there is no reason to have a shared interface for the various families of algorithms.

The best way forward is to introduce a new interface for each algorithm family:

class IAlgoA
{
    virtual int matchCount(T1* p1, T2* p2) = 0;
};

class IAlgoB
{
    virtual int matchCount(T1* p1) = 0;
};

Then you drop the existing IAlgo interface and you fixup the locations where it was used to refer to the new family-specific interfaces.
At this point, adding a third, fourth or fifth family of algorithms does not interfere with the existing families, as they don't have to conform to a shared interface any more.

2

How about simplifying the call to virtual int matchCount() by removing all arguments, and instantiating each Strategy with the arguments of the matchCount method in your question. Then you inject the concrete strategy into your Context.

Strategy pattern applied with variants of matchCount

Here are some ways to use the strategies (I'm not sure how it works in your real problem because there are no details in your question):

Strategy dynamics

6
  • How to inject them, in CTOR? I need to inject them before each call of matchCount. It sounds to me that I need a method called config that injects. But now I will have the same problem with config, right? I should ask how I should polymorphically use conifg if it has different number params?
    – Narek
    Aug 21, 2015 at 16:45
  • You can make a factory to create the strategies if you want to encapsulate that logic from context. I think that strategy always has the same method call. You want that method call to be polymorphic otherwise it's not the strategy pattern. It's hard to answer the question about injection because your code does not show much detail. Aug 21, 2015 at 16:50
  • It calculates match count based on different criteria. So as criteria changes you need to provide more data so that it could count some items in some storage based on that criteria. Please if you see any good way of doing, please write a sample code. I followed @BartvanIngenSchenau approach and created not connected hierarchy for each family, but I am not sure this was the right thing to do.
    – Narek
    Aug 21, 2015 at 16:54
  • I added with an edit some ways you can manage strategies around a context. StrategyManager is not really a class in the pattern, but it's the class that has the responsibility to instantiate and configure concrete strategies. The goal of the pattern is to decouple the context from the strategies. Adding new strategies would require modifying StrategyManager but not Context. Aug 21, 2015 at 17:52
  • I could not really read your diagram. As much as I understand it is to set another parameter before the call of matchCount and again call? Could you please write a code in C++ or Java or C# to demonstrate your diagram? Thanks!
    – Narek
    Aug 21, 2015 at 20:15
0

Optional arguments. Declaring the new arguments as optional allows you to add parameters and not break the existing code.

Overloads. Split the strategy interface into multiple methods.

Messages. Create a hierarchy of argument objects and pass them to the strategy instead of "loose" parameters. The mature calling code, will keep sending the messages, and the mature strategy will keep work with them without breaking. Add new types of messages as needed.

2
  • How can overloads work, if I want to use IAlgos interchangeably? Maybe I don't get your point. Could you elaborate more on overloads, please? Optional arguments smell, if we have lots of them but one only several. And messages sound like downcast will be needed.
    – Narek
    Aug 20, 2015 at 8:53
  • Overloads and optional arguments are bad ideas when it comes to virtual functions. Feb 10, 2016 at 8:01
0

I have encountered this exact problem. I was not satisfied with the way I solved it, so I didn't answer your question right away. But on the other hand, neither does any of the other answers here.

My solution:

class IAlgo
{
    virtual int matchCount(AlgoParameters *) = 0;
}

class AlgoA1 : public IAlgo
{
    virtual int matchCount(AlgoParameters * algoParameters) override
    {
        a1 = algoParameters.a1;
        a2 = algoParameters.a2;
    }
}

class AlgoB1 : public IAlgo
{
    virtual int matchCount(AlgoParameters * algoParameters) override
    {
        b1 = algoParameters.b1;
        b2 = algoParameters.b2;
    }
}

class AlgoParameters 
{
public:
    AParam1 a1;
    AParam2 a1;

    BParam1 b1;
    BParam2 b1;

    CParam1 c1;
    CParam2 c1;
}

As you see I simply encapsuled all parameters for all algorithms into one data-structure. No, this is no nice solution. I do not suggest you use it. But it allows you to do a call like this:

IAlgo GetAlgorithm(EAlgorithmType eAlgoType)
{
    switch(eAlgoType){
    case eAlgoA1:
        return AlgoA1();
    case ...
    }
}
AlgoParameters SetAlgorithmParameters(EAlgorithmType eAlgoType)
{
    AlgoParameters algoParam;
    switch(eAlgoType){
    case eAlgoA1:
        algoParam.a1 = ...;
        algoParam.a2 = ...;
    case ...
    }
}
int UseAlgorithm(IAlgo algoritm, AlgoParameters parameters)
{
    return algorithm.matchCount(&parameters);
}

It violates a lot of principles in software developement (e.g. Open/Close-Principle). But it works and is relatively easily expandable for new algorithms.

1
  • I don't think the general idea of encapsulating parameters (and they might be encapsulated already) violates any principle. Your implementation does, but it's mainly because lack of abstraction. You can't abstract the problem that you know nothing about. Question is answered - what happens in upper layer is beyond the scope of this question.
    – shudder
    Aug 21, 2015 at 20:00

Your Answer

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

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