Question

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?

Était-ce utile?

La solution

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.

Autres conseils

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

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.

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.

Licencié sous: CC-BY-SA avec attribution
scroll top