【问题标题】:Flexible design despite strongly dependent classes尽管类有很强的依赖关系,但设计灵活
【发布时间】:2013-08-20 09:07:49
【问题描述】:

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

我有一个相当复杂的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_fooBar,但一个可能的ConvergenceChecker 只需要知道_foo,另一个可能是_foo_bar,并且可能是一些一天 ConvergenceChecker 需要 _fooBar 将被实施。以下是我已经尝试解决的一些方法:

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

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

补充说明:

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

【问题讨论】:

  • 也许您可以将装饰器模式用于收敛标准。然后,您可以根据需要添加任意数量的不同性质的不同标准,而不会引发争吵。

标签: c++ algorithm convergence


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      使用单独的数据/状态结构似乎很容易 - 只需将其作为只读访问的 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);
                             });
        }
      };
      

      【讨论】:

      • 很好的例子,但为什么要使用嵌套类呢?根据我的经验,它们只会在内部类和外部类之间添加不必要的依赖关系。
      • 根据 OP,检查器、数据和算法已经紧密耦合。如果我们将嵌套类移到外面,我们需要将所有三个放在一个命名空间中,或者给它们(甚至)更长的名称,以防我们将来需要另一个具有类似结构的算法。或者,你知道的,我们自然会称之为State
      【解决方案4】:

      也许decorator pattern 可以帮助简化一组(未知)收敛检查。这样,您可以使算法本身不知道可能发生的收敛检查,并且您不需要容器来进行所有检查。

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

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

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

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

      代码不完整,但你明白了。此外,如果您是性能狂热者并且害怕虚函数调用 (thisCheck),您可以应用 curiously returning template pattern 来消除这种情况。


      这是一个完整的装饰器示例,用于检查 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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-06-10
        • 1970-01-01
        • 1970-01-01
        • 2022-01-24
        • 2017-01-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多