在非虚拟接口习语中添加不变量

时间:2014-02-14 18:17:42

标签: c++ design-patterns idioms template-method-pattern non-virtual-interface

假设我使用NVI习语具有以下层次结构:

class Base
{
    public:
        virtual ~Base() {}
        void foo() { cout << "Base::foo" << endl; foo_impl(); }

    private:
        virtual void foo_impl() = 0;
};

class A : public Base
{
    private:
        virtual void foo_impl() { cout << "A::foo_impl" << endl; }
};

如果在层次结构中的某个时刻,我想&#34;添加&#34;非虚拟基本方法中的不变量,最好的方法是什么?

一种方法是在SpecialBase级别递归NVI习语:

class SpecialBase : public Base
{
    private:
        void foo_impl() { cout << "SpecialBase::foo" << endl; bar_impl(); }
        virtual void bar_impl() = 0;

};

class B : public SpecialBase
{
    private:
        virtual void bar_impl() { cout << "B::bar_impl" << endl; }
};

但我并不喜欢这个想法,因为我不想为我添加到我的层次结构中的每个派生基础添加方法(使用不同的名称)...

另一种方法是拥有以下(不是NVI):

class Base
{
    public:
        virtual ~Base() {}
        virtual void foo() { base_foo(); foo_impl(); }

    protected:
        void base_foo() { cout << "Base::foo" << endl; }
        virtual void foo_impl() = 0;
};

class SpecialBase : public Base
{
    public:
        virtual void foo() { base_foo(); specialbase_foo(); foo_impl(); }

    protected:
        void specialbase_foo() { cout << "SpecialBase::foo" << endl; }
};

class B : public SpecialBase
{
    private:
        virtual void foo_impl() { cout << "B::foo_impl" << endl; }
};

在我看来,这一点不那么令人困惑,因为在任何时候,具体类只需要实现虚方法,而派生基类如果选择也可以覆盖基(虚)方法。

还有另一种更清洁的方法来实现同样的目标吗?

修改

我正在寻找一种非常通用的设计模式,可以让我拥有以下类型的层次结构:

Base <- A
     <- B
     <- SpecialBase <- C
                    <- D
                    <- VerySpecialBase <- E
     <- StrangeBase <- F

每个Base类可以(并将覆盖foo),而类A-F只需要重新实现foo_impl

请注意,添加另一个可选的自定义虚拟函数(例如bar_impl)在这里不会有帮助,因为它只允许一个额外的自定义层,在那里我可能需要无限数量。

2 个答案:

答案 0 :(得分:0)

根据我的理解,NVI是一种防止/阻止向非虚拟基本方法添加不变量的方法,因此此时要添加不变量的事实表明NVI要么不是您要查找的模式完全没有,或者你可能想要重构你的设计,这样你就不需要添加这样的不变量。

这就是说,简单地将以前的非虚拟接口设置为虚拟的替代方法是使用C ++ 11中的final关键字:

class Base
{
    public:
        virtual ~Base() {}
        virtual void foo() { base_foo(); foo_impl(); }

    protected:
        void base_foo() { cout << "Base::foo" << endl; }
        virtual void foo_impl() = 0;
};

class SpecialBase : public Base
{
    public:
        virtual void foo() final // note the use of 'final'
        { base_foo(); specialbase_foo(); foo_impl(); }

    protected:
        void specialbase_foo() { cout << "SpecialBase::foo" << endl; }
};

class B : public SpecialBase
{
    private:
        virtual void foo_impl() { cout << "B::foo_impl" << endl; }
};

这里NVI不是由Base类实现的,而是在SpecialBase级别实现的,因为从SpecialBase派生的类不能再覆盖公共接口(即foo)。

通过这种方式我们说Base的公共接口被允许被覆盖(可以添加不变量,甚至可以重新实现整个函数),但是SpecialBase的公共接口不是。

就我个人而言,我发现这在某些有限的情况下很有用,但大多数时候我只是想在Base中使用更完整的界面。

最终我认为使用Base来清楚地定义允许的自定义点是更常见的:

class Base
{
    public:
        virtual ~Base() {}
        virtual void foo() { base_foo(); bar_impl(); foo_impl(); }

    protected:
        void base_foo() { cout << "Base::foo" << endl; }
        virtual void bar_impl() {} // bar_impl is an optional point of customization
                                   // by default it does nothing

        virtual void foo_impl() = 0; // foo_impl is not optional derived classes
                                     // must implement foo_impl or else they will be abstract
};

class B : public Base
{
    private:
        virtual void bar_impl() { cout << "SpecialBase::foo" << endl; }
        virtual void foo_impl() { cout << "B::foo_impl" << endl; }
};

请注意,根本不再需要SpecialBase类层。

答案 1 :(得分:0)

有人向我建议了这篇帖子,类似于前几天我浏览的与NVI有关的内容,因此是死灵。

我建议在基类中添加一个Check-Adding机制,以便派生类可以添加需求。只要可以使用基类访问功能来测试需求,这就能以非常简单的方式起作用,否则,您特殊的MyInvariant类必须对doCheckInvariantOK()的基参数进行dynamic_cast才能使不变量起作用。

edit:我理解“不变式”遵循foo()的前置条件和后置条件,就像在形式验证中一样。如果要在base_foo()之前和/或之后添加功能(我认为实际上是在此之后),则可以类似的方式进行。

class Base
{
public:
    virtual ~Base() {}
    void foo() 
    { 
        cout << "Base::foo" << endl;

        //Can use invariants as pre and/or postconditions for foo_impl
        for(const std::unique_ptr<InvariantBase>& pInvariant : m_invariants)
        {
            //TODO cout << "Checking precondition " << pInvariant->GetDescription() << endl;
            if(!pInvariant->CheckInvariantOK(*this))
            {
                //Error handling
            }
        }
        foo_impl(); 
    }

protected:
    void AddInvariant(std::unique_ptr<InvariantBase>&& pInvariant)
    {
       m_invariants.push_back(std::move(pInvariant));
    }
    struct InvariantBase
    {
        bool CheckInvariantOK(const Base& base)
        {
            return doCheckInvariantOK(base);
        }
        private:
            virtual bool doCheckInvariantOK(const Base& base) = 0;
    };

private:
    std::list<std::unique_ptr<InvariantBase>> m_invariants;
    virtual void foo_impl() = 0;
};

class A : public Base
{
private:
    virtual void foo_impl() { cout << "A::foo_impl" << endl; }
};

class SpecialBase : public Base
{
public:
     SpecialBase() 
        : Base()
     {
        AddInvariant(std::unique_ptr<MyInvariant>(new MyInvariant() ) );
     }
private:
    void foo_impl() { cout << "SpecialBase::foo" << endl; bar_impl(); }
    virtual void bar_impl() = 0;

    struct MyInvariant : public InvariantBase
    {
        virtual bool doCheckInvariantOK(const Base& base) override
        {
            //TODO: special invariant code
        }
    };

};

class B : public SpecialBase
{
private:
    virtual void bar_impl() { cout << "B::bar_impl" << endl; }
};