0

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

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

如果在层次结构中的某个点我想在非虚拟基础方法中“添加”不变量,那么最好的方法是什么?

一种方法是在 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)在这里没有帮助,因为它只允许一个额外的自定义层,我可能需要一个无限的数量。

4

2 回答 2

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 类层。

于 2014-02-14T19:33:36.183 回答
0

有人向我建议这篇文章类似于我前几天浏览的与 NVI 相关的内容,因此是 necro。

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

编辑:我理解“不变”与 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; }
};
于 2018-07-24T15:13:37.733 回答