4

我正在编写一个本质上需要非常灵活的代码,即以后也很容易被其他人扩展。但我现在面临一个问题,我什至不知道原则上如何正确处理:

我有一个相当复杂Algorithm的 ,在某些时候应该会收敛。但由于其复杂性,有几个不同的标准来检查收敛,并且根据情况(或输入),我希望激活不同的收敛标准。此外,无需触及算法本身,就可以轻松创建新的收敛标准。所以理想情况下,我希望有一个抽象ConvergenceChecker类,我可以从中继承并让算法有一个向量,例如:

//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; }
  }
}

这样做的问题是每个人都ConvergenceChecker需要了解有关当前正在运行的内容Algorithm,但每个人可能需要了解与算法完全不同的事情。说出每个周期中的Algorithm变化_foo _bar和,但一个可能只需要知道,另一个可能只需要知道 ,并且可能有一天会实现需求。以下是我已经尝试解决的一些方法:_fooBarConvergenceChecker_foo_foo_barConvergenceChecker_fooBar

  1. 给函数isConverged()一个大参数列表(包含_foo_bar_fooBar)。缺点:大多数用作参数的变量在大多数情况下都不会使用,并且如果Algorithm将由另一个变量扩展(或类似的算法从它继承并添加一些变量),则必须修改相当多的代码。-> 可能,但丑陋
  2. 将函数本身(或指向它isConverged()Algorithm指针)作为参数。问题:循环依赖。
  3. 声明isConverged()为友元函数。问题(除其他外):不能定义为不同ConvergenceCheckers 的成员函数。
  4. 使用函数指针数组。根本没有解决问题,还有:在哪里定义它们?
  5. (刚刚在写这个问题时想出了这个)使用一个不同的类来保存数据,比如AlgorithmData作为Algorithm一个朋友类,然后提供AlgorithmData作为函数参数。所以,就像 2. 但也许可以解决循环依赖问题。(尚未对此进行测试。)

我很高兴听到您对此的解决方案(以及您在 5. 中看到的问题)。

进一步说明:

  • 问题标题:我知道“强依赖类”已经说过,很可能有人在设计代码时做错了什么,但我想很多人最终可能会遇到这个问题,并想听听可能性避免它,所以我宁愿保持那个丑陋的表情。
  • 太简单了?:其实我这里提出的问题并不完整。代码中会有很多不同Algorithm的 s 相互继承,ConvergenceChecker当然即使有新Algorithm的 s 出现,s 也应该在适当的情况下理想地工作而无需任何进一步的修改。也欢迎对此发表评论。
  • 问题风格:希望问题既不太抽象也不太特别,希望不要太长,通俗易懂。因此,请不要犹豫,对我提出这个问题的方式发表评论,以便我可以改进。
4

4 回答 4

3

实际上,您的解决方案 5 听起来不错。

当有引入循环依赖的危险时,最好的补救措施通常是提取两者都需要的部分,并将其移动到单独的实体中;就像在您的情况下将算法使用的数据提取到单独的类/结构中一样!

于 2013-08-20T09:13:40.020 回答
1

另一种解决方案是向您的检查器传递一个对象,该对象提供当前算法状态以响应以字符串名称表示的参数名称。这使得单独编译您的转换策略成为可能,因为即使您在算法中添加更多参数,这个“回调”接口的接口也保持不变:

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;
};

这就是收敛检查器的实现者需要看到的所有内容:他们实现了检查器,并获得了状态。

您现在可以构建一个与您的算法实现紧密耦合的类,以AbstractAlgorithmState根据其名称实现和获取参数。但是,这个紧密耦合的类对您的实现来说是私有的:调用者只能看到它的接口,它永远不会改变:

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);
}
于 2013-08-20T09:24:14.613 回答
0

也许装饰器模式可以帮助简化一组(未知的)收敛检查。这样,您可以使算法本身与可能发生的收敛检查无关,并且您不需要所有检查的容器。

你会从这些方面得到一些东西:

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 */ }
};

然后,您可以进行任意复杂的收敛检查组合,同时只保留一个ConvergenceCheck*成员Algorithm。例如,如果要检查两个条件(在Check1和中实现Check2):

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

代码不完整,但你明白了。此外,如果您是性能狂热者并且害怕虚函数调用 ( thisCheck),您可以应用奇怪返回的模板模式来消除这种情况。


这是一个完整的装饰器示例,用于检查 上的约束int,以了解其工作原理:

#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);
    
}

输出:

0 1 1

1 1 0

0 1 0

于 2013-08-20T09:28:52.287 回答
0

使用单独的数据/状态结构似乎很容易——只需将它作为只读访问的 const 引用传递给检查器。

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);
                       });
  }
};
于 2013-08-20T09:16:11.440 回答