user1766172
user1766172

Reputation: 93

Adding invariants in non virtual interface idiom

Suppose I have the following hierarchy using the 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; }
};

If at some point in the hierarchy I want to "add" invariants in the non virtual base method, what would be the best way to do so ?

One way would be to recurse the NVI idiom at the SpecialBase level :

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

But I don't really like this idea, since I don't want to add methods (with different names) for each derived bases I add to my hierarchy...

Another way is to have the following (which is not 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; }
};

Which in my opinion is less confusing since at any point a concrete class just has to implement the virtual method, while a derived base class can override the base (virtual) method if it chooses too.

Is there another cleaner way to achieve the same ?

EDIT:

I'm looking for a very general design pattern that could allow me to have the following kind of hierarchy :

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

Where each Base class can (and will override foo), whereas classes A-F will only need to reimplement foo_impl.

Note that just adding another optional customization virtual function (e.g bar_impl) won't help here, because it only allow for one extra layer of customization, where I could possibly need an infinite number.

Upvotes: 0

Views: 147

Answers (2)

Daniel
Daniel

Reputation: 1447

This post was suggested to me as similar to something I was browsing related to NVI the other day, hence the necro.

I would suggest adding a Check-Adding mechanism in the base class, so that derived classes can add requirements. This works in a very straightforward way as long as the requirements can be tested using the base class access functions, otherwise your special MyInvariant class has to dynamic_cast the base argument of doCheckInvariantOK() for the invariant to work.

edit: I understand 'invariant' to be along the lines of pre- and post-conditions of foo(), as in formal verfication. If you want to add functionality before and/or after base_foo(), what I think you're actually after, you can do it in an analogous fashion.

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

Upvotes: 0

YoungJohn
YoungJohn

Reputation: 956

In my understanding, NVI is a way to prevent/discourage adding invariants to the non-virtual base method, so the fact that you want to add invariants at this point suggests that NVI either isn't the pattern you are looking for at all, or you might want to restructure your design so that you do not need to add such invariants.

That being said an alternative to simply making your previously non-virtual interface virtual would be to employ the final keyword from C++11:

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

Here NVI is not implemented by the class Base, but is implemented at the level of SpecialBase since classes derived from SpecialBase can no longer override the public interface (namely foo).

In this way we are saying that the public interface of Base is allowed to be overridden (invariants may be added, or even the entire function may be reimplemented), but the public interface of SpecialBase is not.

Personally I find that this can be useful in some limited cases, but most of the time I simply wanted a more complete interface in Base in the first place.

Ultimately I think it is more common to use Base to clearly define what points of customization are allowed:

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

Note that there is no longer a need for the SpecialBase class layer at all.

Upvotes: 0

Related Questions