ma*******@googlemail.com wrote:
Is this design well-formed? It contradicts the LSP and Design by
contract anyhow. LSP tells us that "In class hierarchies, it should be
possible to treat a specialized object as if it were a base class
object."
LSP doesn't really say this at all, but it is one consequence. To
understand LSP properly we have to understand polymorphism a bit
better.
There are three types of polymorphism, and C++ supprts all three (in
these explanations a client is the function or method that makes use of
the 'polymorphic' type).
* Inclusional polymorphism is what you are talking about. It is where a
client is given a sub-class as a substitute for a superclass and is
what most people (at least in the OO world) think of when polymorphism
is raised.
* Operational polymorphism is where a client is written in terms of the
operations that the type supports. std::min is a good example of this.
Any type can be used so long as it supports <. Most of the STL is
written using this principle of substitutability and it is often called
genericity or in other languages 'duck typing'.
* Parametric polymorphism is where the client changes depending on the
type given it. An example of this is the specialisation of std::vector<
bool to give a much more memory efficient representation.
In practice LSP talks about the substitution of one instances of one
type for an instances of another type and discusses the equivalence
*from the point of view of the client code*. This means that members of
a class hiearchy may or may not be subsitutable depending on how the
client uses the instances.
>
This is the design in question:
[snip]
Now, if I were to pass a Plotter printer to the test function,
Plotter::print would throw a bad_cast exception, because it prints
drawings only.
What exactly went wrong here? Is a Plotter not a printer, after all?
(Same would happen to the LinePrinter.)
There is a general OO question here, and there is the specific case for
the printers.
It often helps to consider responsibilties when thinking about class
designs. The document is clearly responsible for storing and
manipulating its content and the printer class is responsible for
controlling the printer. Your problem amounts to working out which of
these two classes should manage the translation between the
representations.
The correct answer is neither of them. For this situation you should
put a mediation class between them that handles the translation. This
mediation class is now responsible for turning the document's
representation into one suitable for a given printer.
For the specifics of the printers, we might identify three types of
printer - your line printer that can only print plain text, the plotter
which will print a vector representation of the document and a more
normal printer which prints a raster which is normally derived from
text, raster and vector forms.
For each of these outputs you might define a top level class that the
mediator can talk to. Different mediators will talk to different top
level classes. We can then use multiple inheritence on a given printer
implementation depending on which mediators it can accept input from.
Examples of mediators might be 'Strip non-text', 'Rasterise to a given
DPI', 'Rasterise images, keep text' etc
If you design it correctly then the mediators can also be chained which
means you can place 'Strip colour' as a mediator at the start of a
chain before connecting that into a second mediator just before the
printer at the end.
You will probably find that the same technique can give you export
capability to different file formats by suitably choosing the mediators
and 'printing' to a file, for example JPEG or HTML.
In more general programming terms what you're doing here is decoupling
the printer and the document. By doing this decoupling you'll find that
you have a design that is:
* easier to work with and understand because the scope of each class is
smaller (higher coherence);
* much more powerful because it is easy to extend (due to lower
coupling).
A bit short on C++, but hopefully useful nonetheless :)
K