Design Pattern

Non-Virtual Interface Pattern / Non-Virtual Idiom (NVI)

The non-virtual interface pattern (NVI) controls how methods in a base class are overridden. Such methods may be called by clients and overridable methods with core functionality.

It is a pattern that is strongly related to the Template Method Pattern.

Introduced in CS247.

Motivation

public virtual methods are trying to do two things at once:

  • public: Providing an interface to the client
    • will uphold invariants
    • Respect pre-conditions / post-conditions
    • expected to do its job
  • virtual: providing an interface to subclasses
    • Virtual methods are those that may be customized while an object is used polymorphically
    • Provide “hooks” to allow customization of behaviour

Satisfying the responsibilities of “public” and “virtual” simultaneously is difficult

  • How are we sure a public virtual method will satisfy its invariants, pre-conditions / post-conditions. Will it even do its job?
  • What if we want to add more code to the public virtual method without changing the interface?

Non-Virtual Idiom

Non-Virtual Idiom states the following:

  • All public methods are non-virtual
  • All virtual methods should be private (or protected)
  • Exception for the Destructor, which is public

Example: DigitalMedia

class Digitalmedia { // No NVI here
	public:
		virtual ~DigitalMedia() {}
		virtual void play() = 0;
}

With NVI:

class DigitalMedia {
	virtual void doPlay() = 0;
	public:
		virtual ~DigitalMedia() {}
		void play() {
			doPlay();
		}
}

Now: We can add code that must run for all types of DigitalMedia before or after the doPlay call.

  • we could copyright checking before doPlay, this will always run
  • Or: update a play count after doPlay - now subclasses don’t have to worry about maintaining this invariant

Flexible: can provide more “nodes” for customization simply by adding more private virtual method calls.

E.g.: showArt() before doPlay() - private virtual display poster for movie, album cover for song, etc.

All can be done without changing the public interface! Which is good for minimal recompilation, open / closed principle.

  • In general, easier if we constraint what subclasses do from the beginning, as opposed to wresting back control. Supporting Liskov Substitution Principle
  • Any decent compiler will optimize the extra function call out - so no cost at runtime