【问题标题】:How to factorize code properly?如何正确分解代码?
【发布时间】:2011-10-06 19:52:17
【问题描述】:

我有一个非常基本的编程问题。想象一下,我有两个函数,它们的定义实际上是相同的,只是它们仅在内部二进制条件下有所不同。这两个函数中的其余代码实际上是相同的。

为了有一个可读且易于维护的代码,我想知道除了使用布尔参数来选择这些函数的操作模式是否有更好的解决方案?有没有design-pattern 呢?

在下面的代码中,我用两个名为doA()doB() 的函数来说明我的疑问。点 (....) 对应于两个函数完全相同的代码。 我创建了一个新的doNew() 函数,并带有一个额外的布尔参数来选择适当的功能。但是,请注意,尽管这是一种可能的解决方案,但由于 if 两个条件的正文中存在重复代码,它仍然效率低下。

void doA( ..... ){
     .....
     .....
         if(x!=y){
             ....
             ....
             ....
         }
     .....
}

void doB( ..... ){
     .....
     .....
         if(x==y){
             ....
             ....
             ....
         }
     .....
}

void doNew( ....., bool selectionMode ){
     .....
     .....
         if(selectionMode == true){
             if(x==y){
                 ....
                 ....
                 ....
             }
         }
         else{
             if(x!=y){
                 ....
                 ....
                 ....
             }

         }
     .....
}

【问题讨论】:

    标签: c++ function methods


    【解决方案1】:

    我还会使用一个布尔参数来区分它们。对于这么简单的过程,我不会使用复杂的模式。

    我会用

    void doIt(..., BOOL is_equal) {
       ...
       if((a == b) == is_equal) { // or: is_equal ^ (a == b)
          ...
       }
       ...
    }
    

    最终减少冗余。我也会定义独立的名字

    void doA(...) {
        doIt(..., true);
    }
    
    void doB(...) {
        doIt(..., false);
    }
    

    因为我认为 API 中的标志参数不好。

    【讨论】:

    • 这是一个异或,所以如果它是真的,它会反转a == b的结果。所以你甚至不需要复制 if 子句的主体。
    • 谢谢!使用 XOR 有多普遍?由于该代码将来可能会被其他人使用,我希望人们可以快速了解发生了什么。
    • @Peter:如果您认为 XOR 不够自描述,您可以重命名您的 selectionMode resp。 @marc 的 flagbool should_be_equal,然后使用 if ((a==b) == should_be_equal) {},其作用基本相同。
    • @leftaroundabout:这是恕我直言最好的方法,我更新了答案
    • @marc 我假设 doIt() API 应该是一个私有方法,以防我们处理类,对吧?这样我们就强制给定对象只能访问 doA() 或 doB()。
    【解决方案2】:

    许多人建议使用函子,这是个好建议。对您来说最棒的是 == 和 != 的仿函数等价物以及其他此类比较运算符已经作为标准 C++ 库的一部分存在。这是我的做法:

    #include <functional>
    #include <iostream>
    
    template <class T, template <class T> class BinaryFunctor>
    void doSomething(T x, T y, BinaryFunctor<T> f) {
      if (f(x, y)) {
        std::cout << "True" << std::endl;
      } else {
        std::cout << "False" << std::endl;
      }
    }
    
    int main(int argc, const char* argv[])
    {
      doSomething(5, 5, std::equal_to<int>());
      return 0;
    }
    

    doSomething 模板函数接受两个相同类型的参数 T 和一个类型为 BinaryFunctor&lt;T&gt; 的参数。请注意,T 出现在所有三个参数中,因此在传递的所有参数中必须相同。所以传递两个ints 和一个std::equal_to&lt;int&gt; 很好(就像我在示例中所做的那样),因为T 可以实例化为intBinaryFunctorstd::equal_to

    std::equal_to 是标准库的比较函数对象(或函子)之一的示例。它只是一个覆盖operator() 的类,因此std::equal_to 类型的对象可以像真正的函数一样使用。因此,当std::equal_to 类型的对象作为f 参数传递给函数时,您可以像f(someInt, anotherInt) 一样使用它。

    现在,如果您想将比较运算符更改为!=,只需将函数调用更改为doSomething(5, 5, std::not_equal_to&lt;int&gt;());,它就会按预期工作。您还可以找到其他仿函数,例如(省略 std 命名空间):greaterlessgreater_equalless_equal 等。

    【讨论】:

    • 能否请您说明如何在类中定义/声明这种方法?就我而言,我有一个 templated 类,其定义在 .h 文件中,其声明在 .cpp 文件中(如 parashift
    • 我找到了方法!对于模板初始化,它应该是 sth。喜欢:template void CFoo&lt;double&gt;::doSomething(double, double, std::equal_to&lt;double&gt;);
    【解决方案3】:

    您不需要模式,您需要重构技术。

    我倾向于写四个函数让你得到

    DoA()
    {
       Part1();
       SpecificForA();
       Part2();
    };
    
    DoB()
    {
       Part1();
       SpecificForB();
       Part2();
    };
    

    您可能需要在它们之间共享一些变量作为参数。这些函数可能是有用的、可重用的函数。更有可能的是,您会发现需要进行更多重构。现在这更容易了,因为在提取这些函数后,您更清楚它们的作用。

    【讨论】:

      【解决方案4】:

      你可以这样写:

      if(selectionMode && x==y || !selectionMode && x!=y)
          //....
      

      还有其他方法可以做到这一点。您可以传递一个进行比较的函子(例如:一个函数指针)。您可以使用仿函数模板参数编写模板,并使用模板定义这两个函数。

      更新:

      人们提供了几个示例,因此我将向您展示一个不同的示例,这个示例使用普通函数指针,并且不传递两个值进行比较,而是传递比较的结果。 (注意:其他例子是首选,但你看到的例子越多越好)

      //type of the function pointer
      typedef bool (*dofuncptr)(bool);
      
      bool do_istrue(bool b) {
          return b;
      }
      
      //negates the input
      bool do_isfalse(bool b) {
          return b;
      }
      
      void doX(dofuncptr fun) {
          //...
          if (fun(x == y)) {
              //....
          }
          //...
      }
      
      int main() {
          //you can use it like this:
          doX(&do_istrue);
      }
      

      【讨论】:

      • 谢谢!您介意举例说明如何将functor 应用于此特定情况吗?我对functors不是很熟悉。
      • 感谢您的示例!在哪些情况下,您的第二种解决方案可能更受欢迎?
      • 这只是一个示例,向您展示如何使用函数指针。我不会将它用于此任务,但是,随着您了解更多内容,以后可能会有用。
      【解决方案5】:

      对于动态解决方案,我会使用指针函数。

      void(*doNew)(...);
      doNew = &doA;
      doNew(...);//now calls doA
      doNew = &doB;
      doNew(...);//now calls doB
      

      对于静态解决方案... 我会使用值类型模板函数。 前任。

      template<bool TMode>
      void doNew( .....);
      template<>
      void doNew<TRUE>( .....);
      {
           .....
           .....
                   if(x==y){
                       ....
                       ....
                       ....
                   }
           .....
           .....
      }
      template<>
      void doNew<FALSE>( .....);
      {
           .....
           .....
                   if(x!=y){
                       ....
                       ....
                       ....
                   }
           .....
           .....
      }
      

      然后你可以像...一样使用它

      doNew<FALSE>(...); // is equivalant to doNew(..., false);
      

      【讨论】:

        【解决方案6】:

        我会说你将do 作为模板函数和二进制函数作为函数的函子参数。比如:

        template <class BinaryFunctor>
        void do(...,BinaryFunctor f)
        

        这个二元仿函数将返回一个bool,您可以在do 中使用它。

        仿函数示例代码:

        struct Equals
        {
            Equals(int x , int y) : m_x(x), m_y(y){}
            bool operator()() const { return m_x == m_y;}
        
        private:
            int m_x;
            int m_y;
        };
        struct NotEquals
        {
            NotEquals(int x , int y) : m_x(x), m_y(y){}
            bool operator()() const { return m_x != m_y;}
        
        private:
            int m_x;
            int m_y;
        };
        template<class BinaryFunctor>
        void doSomething(BinaryFunctor f)
        {
            if(f())
            {
                //Condition satisfied
            }
        }
        int main () 
        {
            doSomething(Equals(10,11));
            doSomething(NotEquals(10,11));
            return 0;
        }
        

        【讨论】:

        • 您介意说明您提出的解决方案吗?我对functors 不是很熟悉:S.
        • 函子实际上只是一个重载operator()的类,因此可以像函数一样调用它。给定一个对象f,它有一个operator()(int x, int y),你可以调用f(x, y)。就像f 是一个函数一样。但不同的是,您实际上是在类类型上调用运算符,它可能具有额外的状态,因此更加灵活
        • @Naveen,谢谢!我猜函子也可以模板化,对吧?在==!= 的情况下,也可以将它与boolfloat 等变量类型一起使用,对吧?
        • @Peter:是的,当然。我的只是一个展示 synatx 的例子。事实上,STL 有很多有用的函子,例如std::equal_to&lt;&gt;,可以在这里使用。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-02
        • 1970-01-01
        相关资源
        最近更新 更多