【问题标题】:The best design for a derived class that extends functions from a base class从基类扩展函数的派生类的最佳设计
【发布时间】:2016-08-30 01:06:53
【问题描述】:

我有一个代码,其中派生类实现的函数是基类中相同的扩展。在下面的示例中,我希望函数 do_work 执行一系列任务。在派生类Derived中,do_work函数包含了基类Basedo_work函数的所有工作,包括一项额外的任务。

从软件设计的角度来看,最优雅的方式是什么?使用OPTION 1 还是使用OPTION 2

#include <iostream>

class Base
{
    public:
        virtual void do_work()
        {
            do_base_task1();
            do_base_task2();
        }
    protected:
        void do_base_task1() { std::cout << "Doing base task1" << std::endl; }
        void do_base_task2() { std::cout << "Doing base task2" << std::endl; }
};

class Derived : public Base
{
    public:
        void do_work()
        {
            // OPTION 1
            do_base_task1();
            do_base_task2();
            // END OF OPTION 1

            // OPTION 2
            Base::do_work();
            // END OF OPTION 2

            do_extra_task();
        }
    protected:
        void do_extra_task() { std::cout << "Doing derived task" << std::endl; }
};

int main()
{
    Base base;
    base.do_work();

    Derived derived;
    derived.do_work();

    return 0;
}

【问题讨论】:

  • 我建议选项 2。选项 1 违反了 DRY 原则。看看Template method pattern
  • 我更喜欢选项 2,因为这意味着您可以创建 do_base_task1()do_base_task2() 私有成员函数而不是受保护的 - 它们只能从 Base 中访问,因为派生类没有企业打​​电话给他们。
  • 此外,选项 2 允许您将受保护的方法设为私有,这有助于封装。
  • @smkanadl 这怎么违反它?请解释一下。
  • 只要派生类中的所有任务都在基本任务之后,我肯定也会选择选项 2。我最近遇到了类似的问题,我基本上不得不像do_base_task1()do_extra_task()do_base_task2() 这样的东西。在那里我选择了选项 1,因为我没有看到另一种直接的方法。

标签: c++ class oop inheritance


【解决方案1】:

在我看来,最有效的版本是:

void do_work() {
  Base::do_work();
  do_extra_task();
}

因为这样,如果您更改 Base::do_work 实现,您也不必传递派生类成员函数 Derived::do_work 中的更改。

【讨论】:

    【解决方案2】:

    除了101010的回答,做

    void do_work() {
        Base::do_work();
        do_extra_task();
    }
    

    允许您创建do_base_task1()do_base_task2() 私有成员函数而不是protected,从而改进结构和封装。 Derived 类没有调用这两个方法的业务,因此将它们设为私有并提供一个公共接口方法来调用这些方法(即Base::do_work())是更好的设计。

    【讨论】:

      【解决方案3】:

      选项 3:

      class Base
      {
          public:
          void do_work()  // not virtual
          {
              do_base_task1();
              do_base_task2();
              do_extra_task();
          }
      
          //... base tasks as before
      
          protected:
          virtual void do_extra_task(){}
      };
      
      class Derived : public base
      {
          //no do_work in here now
          protected:
          void do_extra_task() { std::cout << "Doing derived task" << std::endl; }
      }
      

      以与您的示例相同的方式在 main 中调用

      这样你就知道所有派生类都会调用基类做工作(你不能在其中一个派生类中忘记它),如果一个特定的派生类没有额外的工作要做,那么它就不需要定义 do_work 或 do_extra_tasks。减少重复,因为您不重复定义 do_work。

      这是非虚拟接口模式。

      【讨论】:

      • 这将不允许人们在不编辑基类的情况下添加额外的功能。
      • 为什么不呢?您可以通过覆盖派生类中的虚拟 do_extra_task 函数来添加额外功能。它确实阻止派生类编写避免基类操作的 do_work,因此如果它们是可选的,这将不是要使用的模式,但是如果所有派生类都必须调用基类工作,那么这段代码是自记录的在这方面。这是一个既定的模式。 gotw.ca/publications/mill18.htm
      【解决方案4】:

      另一种选择:

      #include<iostream>
      
      struct B {
          virtual void doWork() = 0;
      };
      
      template<class D>
      struct T: B {
          void doWork() override {
              taskT();
              static_cast<D*>(this)->taskD();
          }
      
      private:
          void taskT() { std::cout << "taskT" << std::endl; }
      };
      
      struct S: T<S> {
          void taskD() { std::cout << "taskD" << std::endl; }
      };
      
      int main() {
          B *b = new S;
          b->doWork();
      }
      

      这是基于 CRTP 的成语。
      如果您正在设计一个小型库,则此选项将完全负责在调用正确的方法时调用它。
      你的库的用户甚至不知道基类中的方法存在,但知道派生类的方法将被契约调用。
      更重要的是,taskD 不是基类接口的一部分,所以你不能从对B 的引用中执行额外的任务(除非你调用doWork,它实际上以正确的顺序执行所有任务)。

      【讨论】:

        猜你喜欢
        • 2013-04-27
        • 1970-01-01
        • 2015-04-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-19
        • 1970-01-01
        相关资源
        最近更新 更多