Tuesday 11 March 2014

Design patterns - Non virtual interface

Here I am going to talk about non virtual interface (NVI) to achieve virtual behavior.
1)  Here are 2 implementations.-----------------------------------------------------------------------------
1.1) Idea: derived class to override private virtual function which are the real implementation of public functions.

class NVI {
public: // public interface
    void Foo() {
        FooImp();
    }
    void Bar() {
        BarImp();
    }
private:
    // or declare as pure virtual function if no implementation
    virtual void FooImp();
    virtual void BarImp();
};

class DerivedNVI {
    void FooImp();
    void BarImp();
};
// User code
NVI& nviRef = DerivedNVI();
nviRef.Foo();
nviRef.Bar();

1.2) Idea: use delegation class to encapsulate all (private data and) virtual behaviors
class NviDelegator;
class NVI {
public:
    void Foo() {
        m_Delegator->FooImp();
    }
    void Bar() {
        m_Delegator->BarImp();
    }

    static NVI* CreateInstance(/*option to create proper delegator*/){
        return new NVI();
    }  

    friend class NviDelegator; // depends if this delegate needs to access NVI's data
private:
    NVI(); // make it private, have to call CreateInstance to create objects
    NviDelegator* m_Delegator; // concrete composite relationship
};

class NviDelegator {
public:
    // all public interface to accomodate NVI's virtual behavior
    // declare virtual if needed
    void FooImp();
    void BarImp();
private:
    // all private data member from NVI  to here
    // if not, use friend class relationship
    NVI& m_NVI; // depends if this delegate needs to access NVI's data member
};

2) Compare it with virtual interface (VI)
The best known is to use pure virtual functions in abstract class.
class VirtualInterface {
public:
    virtual void Foo() = 0;
    virtual void Bar() = 0;
};
Most of cases they are equally good for most purposes. NVI normally has rigid and fragile interfaces and are difficult to maintain and easy to break. But it is flexible when coming to the relationship with delegates, which has more flexibility in implementation and loose coupling. VI is more flexible in maintaining the API functions but put extra strain on the relations between classes. Any class would have to inherit from it and implement all of them. Sometimes even when "is-a" relationship isn't there. Normally bad design/implementation will arise for instance the infamous diamond inheritance and private inheritance.

3) Recommendation
Definitely use NVI as public API when codes can be organized into modules. For instance build codes into DLL and shared libraries, or de-couple the code. If given option, always use the implementation in Section 1.2, as it provides better code de-coupling.

Use VI if doing the abstraction and encapsulation between classes. And this VI is not used as public API or you have full access to the code.

Someone may argue that VI can do same the job of NVI acting as the public API by adding creation function, like:
class VirtualInterface {
public:
    virtual void Foo() = 0;
    virtual void Bar() = 0;
    static VirtualInterface* CreateInstance(/*option here to create concrete classes*/);
};
It is true that both NVI and VI will do the job. But here in this situation NVI will prevent from being inherited in sense of virtual behavior. However VI can be inherited and its functionality can be overridden. It is dangerous potentially. Another impact is coming from user code. NVI is better than VI. NVI is free to expand API and user code will not be affected. However every time VI expands, user code will break.







No comments:

Post a Comment