[Exceptional C++ Style] Item 18 - Virtuality
Kevlin Henney
kevlin at curbralan.com
Tue Jan 4 09:17:21 EST 2005
In message <41DA9608.2040404 at jaggersoft.com>, Jon Jagger
<jon at jaggersoft.com> writes
>
>>C++ standard library already follows this guideline.
>>The standard library has:
>>6 public virtual functions, and 142 nonpublic virtual
>>functions.
>>
>>The author mentions one WinFX (the object-oriented
>>successor to the Win32 API and the programming model
>>for Longhorn) design guideline which recommends using
>>‘Template Method’ pattern and discusses why this
>>pattern is such a good idea.
>>
>Playing the devils advocate much of the design of the C++ library and
>Win32 API leaves one feeling, how shall I put it, somewhat "empty"
A very good way of putting it. With the exception of the STL, there is
much in the standard library that leaves the reader empty but the
library rather full :->
One must always be aware of playing with numbers: anyone familiar with
statistics will recognise that the numbers in question do not support
any specific hypothesis. When it comes to class hierarchy design the
standard library is filled with many counterexamples, which makes good
design that much harder to teach to people :-( Such simple numbers
should not be thrown about lightly and without qualification.
>>Traditionally, programmers write base classes using
>>public virtual functions which simultaneously specify
>>both the interface and the customizable behaviour.
>>
>Surely it is a base class that contains any implementation that is
>"guilty" of specifying both the interface and some behaviour (whether
>it be customizable or not)?
Indeed. If the problem is that programmers provide virtual functions
that both specify public contract and a specific behaviour, then the
solution is to adopt a practice where virtual functions specify the
public contract and not a specific behaviour. This much has been known
for some time, and is a practice supported more directly in the
language, ie pure virtual functions, and one that does not get you into
the problems of overcomplicating classes and introducing too many middle
players for their own sake.
When I teach class hierarchy design, one of the considerations I
advocate is that pure virtual functions should be overridden and
non-pure virtual functions should not be. In other words, provide new
implementations but don't replace existing implementations. This has
some very useful consequences in terms of design and design questions,
and tends to lead to more clearly structured class hierarchies, with
clearer responsibilities and looser coupling than most other approaches.
What you end up with is a hierarchy that is purely abstract at its roots
and fully concrete at its leaves, with the middle taking the slack and
strain of implementation refactoring. There are many ways of justifying
such an architectural approach, but the view that you should not
disinherit code is perhaps the simplest in terms of generating such an
architecture.
The role of the middle layer is to factor out commonality in the
concrete layer, and it is here that use of Template Method or, often
better, a delegation-based alternative such as Strategy, Interceptor,
Object Adapter, etc, can come into play. It is in this layer that NVI
could be seen to have a possible role. However, unless an application of
NVI actually factors out some commonality, it may be better to Remove
Middle Man (http://www.refactoring.com/catalog/removeMiddleMan.html).
>>Every public virtual function is forced to serve two
>>audiences, the outside callers of the class and
>>derived classes.
>But if the base class contains only pure virtual functions it serves
>the purpose of representing the point where the outside and the inside
>touch. And it does not get weighed down with supposed commonality. What
>is wrong with having a clear separation of concerns and putting
>commonality into an ABC (containing some implementation) below the
>interface (containing only pure virtual functions). Pushing
>implementation up into the base class is what causes the base class to
>start serving two masters.
That point about serving two masters is one that can be taken further.
To say that a virtual function must serve two audiences and use this as
a criticism is to miss much of the point of virtual functions: the
purpose of a virtual function is serve as a bridge between the caller of
the function and the implementation in the derived class. That is not a
problem that needs another level of indirection to solve, it is the
whole point!
There are already two sides to a virtual function: its signature (the
promise) and the definition (the fulfilment). It makes sense, reasoning
in terms of cohesion alone, to separate these two sides, which gives
rise to the recommendation of having pure virtual functions in an
interface class and overriding virtual functions in derived concrete
classes. Adding a further level of separation smothers and hides this
important semantic and syntactic connection, weakening the intent of the
interface and diluting its contract. It is reasonable to partition and
subcontract work to derived classes (commonplace Template Method), but
to always subcontract all of the work in one package does not
automatically make sound business or software sense. It makes sense to
cut out the fat and work directly with the abstractions, rather than
further abstract the abstractions.
Kevlin
--
____________________________________________________________
Kevlin Henney phone: +44 117 942 2990
mailto:kevlin at curbralan.com mobile: +44 7801 073 508
http://www.curbralan.com fax: +44 870 052 2289
Curbralan: Consultancy + Training + Development + Review
____________________________________________________________
More information about the Effective-cpp
mailing list