【问题标题】:Adding invariants in non virtual interface idiom在非虚拟接口习语中添加不变量
【发布时间】:2014-03-14 05:54:21
【问题描述】:

假设我有以下使用 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; }
};

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

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

【问题讨论】:

  • “因为我不想为我添加到我的层次结构中的每个派生基添加方法(具有不同的名称)” 我不确定这是不是很糟糕主意。毕竟,您想添加不变量,即派生类也需要维护这些新的不变量。
  • 考虑到您的编辑,我几乎想知道装饰器模式是否会为您提供所需的自定义层。

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


【解决方案1】:

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

我建议在基类中添加一个检查添加机制,以便派生类可以添加需求。只要可以使用基类访问函数测试需求,这就会以非常简单的方式工作,否则您的特殊 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; }
};

【讨论】:

  • 有趣。我个人会选择std::vector&lt;std::function&lt;bool()&gt;&gt;:然后您就可以将不变量注册为 lambda 表达式,它可以从派生的构造函数中捕获它们的上下文,因此无需向下转换。
  • @Quentin:你能详细说明一下吗? lambda 表达式如何知道 doCheckInvariantOK() 中的构造上下文?
  • 我的意思是在SpecialBase::SpecialBase 中以AddInvariant([] { return /* check */; }); 结尾。 lambda 可以使用正确的静态类型捕获 this,然后通过 std::function 而不是 OO 多态性完成类型擦除。
【解决方案2】:

据我了解,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 类层。

【讨论】:

  • 我理解你的意思,很可能 NVI 不是我想要的。但是,我正在寻找一种更通用的习惯用法,而不仅仅是具有可选的自定义点……我的层次结构中需要 SpecialBase,它不仅覆盖 foo() 的实现。也可能有从 SpecialBase 派生的基类,它们将再次重新实现 foo() .. 所以我真的在寻找更通用的设计模式。我编辑了我的问题以添加我正在寻找的真实工作流程。
猜你喜欢
  • 1970-01-01
  • 2011-02-13
  • 2011-01-21
  • 1970-01-01
  • 2020-11-10
  • 2022-01-11
  • 1970-01-01
  • 1970-01-01
  • 2017-12-14
相关资源
最近更新 更多