4

I'm working on a code which needs to be extremely flexible in nature, i.e. especially very easy to extend later also by other people. But I'm facing a problem now about which I do not even know in principal how to properly deal with:

I'm having a rather complex Algorithm, which at some point is supposed to converge. But due to its complexity there are several different criteria to check for convergence, and depending on the circumstances (or input) I would want to have different convergence criteria activated. Also it should easily be possible to create new convergence criteria without having to touch the algorithm itself. So ideally I would like to have an abstract ConvergenceChecker class from which I can inherit and let the algorithm have a vector of those, e.g. like this:

//Algorithm.h (with include guards of course)
class Algorithm {
  //...
  vector<ConvergenceChecker*> _convChecker;
}
//Algorithm.cpp
void runAlgorithm() {
  bool converged=false;
  while(true){
    //Algorithm performs a cycle
    for (unsigned i=0; i<_convChecker.size(); i++) {
      // Check for convergence with each criterion
      converged=_convChecker[i]->isConverged();
      // If this criterion is not satisfied, forget about the following ones
      if (!converged) { break; }
    }
    // If all are converged, break out of the while loop
    if (converged) { break; }
  }
}

The problem with this is that each ConvergenceChecker needs to know something about the currently running Algorithm, but each one might need to know totally different things from the algorithm. Say the Algorithm changes _foo _bar and _fooBar during each cycle, but one possible ConvergenceChecker only needs to know _foo, another one _foo and _bar, and it might be that some day a ConvergenceChecker needing _fooBar will be implemented. Here are some ways I already tried to solve this:

  1. Give the function isConverged() a large argument list (containing _foo, _bar, and _fooBar). Disadvantages: Most of the variables used as arguments will not be used in most cases, and if the Algorithm would be extended by another variable (or a similar algorithm inherits from it and adds some variables) quite some code would have to be modified. -> possible, but ugly
  2. Give the function isConverged() the Algorithm itself (or a pointer to it) as an argument. Problem: Circular dependency.
  3. Declare isConverged() as a friend function. Problem (among others): Cannot be defined as a member function of different ConvergenceCheckers.
  4. Use an array of function pointers. Does not solve the problem at all, and also: where to define them?
  5. (Just came up with this while writing this question) Use a different class which holds the data, say AlgorithmData having Algorithm as a friend class, then provide the AlgorithmData as a function argument. So, like 2. but maybe getting around circular dependency problems. (Did not test this yet.)

I'd be happy to hear your solutions about this (and problems you see with 5.).

Further notes:

  • Question title: I'm aware that 'strongly dependent classes' already says that most probably one is doing something very wrong with designing the code, still I guess a lot of people might end up with having that problem and would like to hear possibilities to avoid it, so I'd rather keep that ugly expression.
  • Too easy?: Actually the problem I presented here was not complete. There will be a lot of different Algorithms in the code inheriting from each other, and the ConvergenceCheckers should of course ideally work in appropriate cases without any further modification even if new Algorithms come up. Feel free to comment on this as well.
  • Question style: I hope the question is neither too abstract nor too special, and I hope it did not get too long and is understandable. So please also don't hesitate to comment on the way I ask this question so that I can improve on that.
user2296653
  • 1,151
  • 1
  • 8
  • 17
  • Maybe you can use the decorator pattern for the convergence criteria. You can then add as many distinct criteria of various nature as you want without them starting a fight. – Marc Claesen Aug 20 '13 at 09:13

4 Answers4

3

Actually, your solution 5 sounds good.

When in danger of introducing circular dependencies, the best remedy usually is to extract the part that both need, and moving it to a separate entity; exactly as extracting the data used by the algorithm into a separate class/struct would do in your case!

codeling
  • 11,056
  • 4
  • 42
  • 71
1

Another solution would be passing your checker an object that provides the current algorithm state in response to parameter names expressed as string names. This makes it possible to separately compile your conversion strategies, because the interface of this "callback" interface stays the same even if you add more parameters to your algorithm:

struct AbstractAlgorithmState {
    virtual double getDoubleByName(const string& name) = 0;
    virtual int getIntByName(const string& name) = 0;
};
struct ConvergenceChecker {
    virtual bool converged(const AbstractAlgorithmState& state) = 0;
};

That is all the implementers of the convergence checker need to see: they implement the checker, and get the state.

You can now build a class that is tightly coupled with your algorithm implementation to implement AbstractAlgorithmState and get the parameter based on its name. This tightly coupled class is private to your implementation, though: the callers see only its interface, which never changes:

class PrivateAlgorithmState : public AbstractAlgorithmState {
private:
    const Algorithm &algorithm;
public:
    PrivateAlgorithmState(const Algorithm &alg) : algorithm(alg) {}
    ...
    // Implement getters here
}
void runAlgorithm() {
    PrivateAlgorithmState state(*this);
    ...
    converged=_convChecker[i]->converged(state);
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
0

Using a separate data/state structure seems easy enough - just pass it to the checker as a const reference for read-only access.

class Algorithm {
public:
  struct State {
    double foo_;
    double bar_;
    double foobar_;
  };
  struct ConvergenceChecker {
    virtual ~ConvergenceChecker();
    virtual bool isConverged(State const &) = 0;
  }
  void addChecker(std::unique_ptr<ConvergenceChecker>);
private:
  std::vector<std::unique_ptr<ConvergenceChecker>> checkers_;
  State state_;

  bool isConverged() {
    const State& csr = state_;
    return std::all_of(checkers_.begin(),
                       checkers_.end(),
                       [csr](std::unique_ptr<ConvergenceChecker> &cc) {
                         return cc->isConverged(csr);
                       });
  }
};
Useless
  • 64,155
  • 6
  • 88
  • 132
  • good example, but why use nested classes? In my experience, they only add an unneeded dependency between inner and outer class. – codeling Aug 20 '13 at 09:18
  • Per the OP, the checker, data and algorithm are _already_ tightly coupled. If we move the nested classes outside, we need to put all three in a namespace or give them (even) longer names, in case we ever want another algorithm with similar structure in the future. Or, you know, anything else we'd naturally call `State`. – Useless Aug 20 '13 at 09:23
0

Maybe the decorator pattern can help in simplifying an (unknown) set of convergence checks. This way you can keep the algorithm itself agnostic to what convergence checks may occur and you don't require a container for all the checks.

You would get something along these lines:

class ConvergenceCheck {
private:
  ConvergenceCheck *check;
protected:
  ConvergenceCheck(ConvergenceCheck *check):check(check){}
public:
  bool converged() const{
    if(check && check->converged()) return true;
    return thisCheck();
  }
  virtual bool thisCheck() const=0;
  virtual ~ConvergenceCheck(){ delete check; }
};
            
struct Check1 : ConvergenceCheck {
public: 
  Check1(ConvergenceCheck* check):ConvergenceCheck(check) {}   
  bool thisCheck() const{ /* whatever logic you like */ }
};

You can then make arbitrary complex combinations of convergence checks while only keeping one ConvergenceCheck* member in Algorithm. For example, if you want to check two criteria (implemented in Check1 and Check2):

ConvergenceCheck *complex=new Check2(new Check1(nullptr));

The code is not complete, but you get the idea. Additionally, if you are a performance fanatic and are afraid of the virtual function call (thisCheck), you can apply the curiously returning template pattern to eliminate that.


Here is a complete example of decorators to check constraints on an int, to give an idea of how it works:

#include <iostream>

class Check {
private:
  Check *check_;
protected:
    Check(Check *check):check_(check){}
public:
  bool check(int test) const{
    if(check_ && !check_->check(test)) return false;
    return thisCheck(test);
  }
  virtual bool thisCheck(int test) const=0;
  virtual ~Check(){ delete check_; }
};

class LessThan5 : public Check {
public: 
  LessThan5():Check(NULL){};
  LessThan5(Check* check):Check(check) {};
  bool thisCheck(int test) const{ return test < 5; }
};

class MoreThan3 : public Check{
public: 
  MoreThan3():Check(NULL){}
  MoreThan3(Check* check):Check(check) {}   
  bool thisCheck(int test) const{ return test > 3; }
};

int main(){

    Check *morethan3 = new MoreThan3();
    Check *lessthan5 = new LessThan5();
    Check *both = new LessThan5(new MoreThan3());
    std::cout << morethan3->check(3) << " " << morethan3->check(4) << " " << morethan3->check(5) << std::endl;
    std::cout << lessthan5->check(3) << " " << lessthan5->check(4) << " " << lessthan5->check(5) << std::endl;
    std::cout << both->check(3) << " " << both->check(4) << " " << both->check(5);
    
}

Output:

0 1 1

1 1 0

0 1 0

Community
  • 1
  • 1
Marc Claesen
  • 16,778
  • 6
  • 27
  • 62