派生类的最佳设计,它扩展了基类的功能

时间:2016-05-04 14:13:16

标签: c++ class oop inheritance

我有一个代码,其中派生类实现的功能是基类中相同的扩展。在下面的示例中,我希望函数do_work执行一系列任务。在派生类Derived中,do_work函数包含基类Base do_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;
}

4 个答案:

答案 0 :(得分:2)

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

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

因为这样改变Base::do_work实现,所以你也不必在派生类成员函数Derived::do_work中传递更改。

答案 1 :(得分:1)

除了101010的答案,还要做

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

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

答案 2 :(得分:1)

选项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。

这是非虚拟接口模式。

答案 3 :(得分:1)

又一个选择:

#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,它实际上执行所有正确的顺序任务)。