473,387 Members | 1,520 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,387 software developers and data experts.

Liskov Substitution Principle

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."

This is the design in question:

//////////////////////// Two abstract data types:

class Document
{
public:
virtual ~Document() {}
};

class Printer
{
public:
virtual void print(Document const&) const=0;
};

//////////////////////// Documents to be printed

class TextDocument: public Document {};
class Drawing: public Document {};

//////////////////////// Derived classes contradicting LSP

class Plotter: public Printer // a Plotter prints drawings only
{
public:
void print(Document const& d) const
{
/* use danymic_cast<Drawing const&>(d) */
}
};

class LinePrinter: public Printer // a LinePrinter prints text only
{
public:
void print(Document const& d) const
{
/* use danymic_cast<TextDocument const&>(d) */
}
};

//////////////////////// test function

void test(Printer const& p)
{
TextDocument td;
p->print(td);
}

//////////////////////// end

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.)

I guess Plotter is not a Printer. Well, it is, but it doesn't print
like a general "printer" (whatever that may be) anyway. So I either
remove the Printer::print method completely and use dynamic_cast a lot
to access Plotter::print, or I don't have Plotter inherited from
Printer (which I don't prefer, because I need a pointer pointing to
various printers).

I rather remove the Printer::print method and use dynamic_cast a lot,
which is ugly. Because this way it still throws exceptions if 'derived'
doesn't happen to be a Printer. Either way, there's always a
dynamic_cast throwing something unpleasant, isn't it?

Oct 13 '06 #1
11 2496


Hi,

Being more of a practical guy. I would say that the design is not very good.
You derive everything from document but since document is so generic that it
actually can't do anything (it has no methods) there is little or no
advantage.

Secondly a plotter can't do everything a printer can therefore it isn't a
specialized version of printer and shouldn't derive from it.

I would say a document is best at knowiing how to print itself. So I could
pass the document the printer object. If plotter and lineprinter have
something in common then that is the part that would go in the printer
object and additional stuff in plotter or lineprinter.

Now document can have a method to print on a printer (the common stuff) a
method to print on the lineprinter and a method to print on a plotter.

Regards, Ron AF Greve

http://moonlit.xs4all.nl

<ma*******@googlemail.comwrote in message
news:11********************@b28g2000cwb.googlegrou ps.com...
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."

This is the design in question:

//////////////////////// Two abstract data types:

class Document
{
public:
virtual ~Document() {}
};

class Printer
{
public:
virtual void print(Document const&) const=0;
};

//////////////////////// Documents to be printed

class TextDocument: public Document {};
class Drawing: public Document {};

//////////////////////// Derived classes contradicting LSP

class Plotter: public Printer // a Plotter prints drawings only
{
public:
void print(Document const& d) const
{
/* use danymic_cast<Drawing const&>(d) */
}
};

class LinePrinter: public Printer // a LinePrinter prints text only
{
public:
void print(Document const& d) const
{
/* use danymic_cast<TextDocument const&>(d) */
}
};

//////////////////////// test function

void test(Printer const& p)
{
TextDocument td;
p->print(td);
}

//////////////////////// end

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.)

I guess Plotter is not a Printer. Well, it is, but it doesn't print
like a general "printer" (whatever that may be) anyway. So I either
remove the Printer::print method completely and use dynamic_cast a lot
to access Plotter::print, or I don't have Plotter inherited from
Printer (which I don't prefer, because I need a pointer pointing to
various printers).

I rather remove the Printer::print method and use dynamic_cast a lot,
which is ugly. Because this way it still throws exceptions if 'derived'
doesn't happen to be a Printer. Either way, there's always a
dynamic_cast throwing something unpleasant, isn't it?

Oct 13 '06 #2

Moonlit wrote:
Hi,

Being more of a practical guy. I would say that the design is not very good.
I agree.
You derive everything from document but since document is so generic that it
actually can't do anything (it has no methods) there is little or no
advantage.
The classes are simplified. There could, of course, be a method like
virtual std::vector<Pixelget_content() const=0;
or something like that in the Document class.
Secondly a plotter can't do everything a printer can therefore it isn't a
specialized version of printer and shouldn't derive from it.
True.
I would say a document is best at knowiing how to print itself. So I could
pass the document the printer object. If plotter and lineprinter have
something in common then that is the part that would go in the printer
object and additional stuff in plotter or lineprinter.

Now document can have a method to print on a printer (the common stuff) a
method to print on the lineprinter and a method to print on a plotter.
And if I happen to have hundreds of printers? Or if I wanted to add
some, I would have to add methods to each class in the Document
hierarchy. I don't know...

Oct 13 '06 #3
HI
<ma*******@googlemail.comwrote in message
news:11**********************@m73g2000cwd.googlegr oups.com...
>
Moonlit wrote:
>Hi,

Being more of a practical guy. I would say that the design is not very
good.
I agree.
>You derive everything from document but since document is so generic that
it
actually can't do anything (it has no methods) there is little or no
advantage.
The classes are simplified. There could, of course, be a method like
virtual std::vector<Pixelget_content() const=0;
or something like that in the Document class.
>Secondly a plotter can't do everything a printer can therefore it isn't a
specialized version of printer and shouldn't derive from it.
True.
>I would say a document is best at knowiing how to print itself. So I
could
pass the document the printer object. If plotter and lineprinter have
something in common then that is the part that would go in the printer
object and additional stuff in plotter or lineprinter.

Now document can have a method to print on a printer (the common stuff) a
method to print on the lineprinter and a method to print on a plotter.

And if I happen to have hundreds of printers? Or if I wanted to add
some, I would have to add methods to each class in the Document
hierarchy. I don't know...
The problem is. If your printers have things in common you only need one
print method. If you want some specialized thing only one printer can do (or
you might be able to group them i.e. make the derived tree a bit deeper)
then you do need more methods.

How are you going to used the specialized features from some printer only
using the generic printing routines, you can't. Object oriented programming
is not some miracle by which you don't have to code it only lets you reuse
code that's already there.
Regards, Ron AF Greve

http://moonlit.xs4all.nl
Oct 13 '06 #4
Hi,
Okay there is another solution but it is a bit ugly. Add some or all
functions from all printers to the printer object but leave them empty. Then
in the derived classes overide the implemented ones.

Not very nice, makes debugging harder, I guess but less coding. I might or
might not work and can break when adding a new printer. For instance when a
printer hass less features than you expected in the initial design, it won't
give a compiler error, since all the (virtual) methods are there.

Regards, Ron AF Greve.
Oct 13 '06 #5
<ma*******@googlemail.comwrote in message
news:11********************@b28g2000cwb.googlegrou ps.com...
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."

This is the design in question:

//////////////////////// Two abstract data types:

class Document
{
public:
virtual ~Document() {}
};

class Printer
{
public:
virtual void print(Document const&) const=0;
};

//////////////////////// Documents to be printed

class TextDocument: public Document {};
class Drawing: public Document {};

//////////////////////// Derived classes contradicting LSP

class Plotter: public Printer // a Plotter prints drawings only
{
public:
void print(Document const& d) const
{
/* use danymic_cast<Drawing const&>(d) */
}
};

class LinePrinter: public Printer // a LinePrinter prints text only
{
public:
void print(Document const& d) const
{
/* use danymic_cast<TextDocument const&>(d) */
}
};

//////////////////////// test function

void test(Printer const& p)
{
TextDocument td;
p->print(td);
}

//////////////////////// end

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.)

I guess Plotter is not a Printer. Well, it is, but it doesn't print
like a general "printer" (whatever that may be) anyway. So I either
remove the Printer::print method completely and use dynamic_cast a lot
to access Plotter::print, or I don't have Plotter inherited from
Printer (which I don't prefer, because I need a pointer pointing to
various printers).

I rather remove the Printer::print method and use dynamic_cast a lot,
which is ugly. Because this way it still throws exceptions if 'derived'
doesn't happen to be a Printer. Either way, there's always a
dynamic_cast throwing something unpleasant, isn't it?
I think the problem is your concept of what a Printer is here. Not all
printers in this scenario can print drawings and text. Why not do something
like this:

class DrawingPrinter
{
public:
virtual void print(const Drawing& d) = 0;
};

class TextPrinter
{
public:
virtual void print(const TextDocument& d) = 0;
};

class Plotter : public DrawingPrinter
{
public:
void print(const Drawing& d)
{
//...
}
};

class LinePrinter : public TextPrinter
{
public:
void print(const TextDocument& d)
{
//...
}
};

You might also have a general printer:

class GeneralPrinter : public DrawingPrinter, TextPrinter {};

class SuperFunkyPrinter : public GeneralPrinter
{
public:
void print(const Drawing& d)
{
//...
}

void print(const TextDocument& d)
{
//...
}
};

In your original code, the problem was that your Plotter isn't a Printer,
even though it represents something that is a printer, if you see the
distinction? In other words, Plotter may represent a real-life plotter,
which is a real-life printer, but not the sort of real-life printer
represented by Printer. You see my point?

HTH,
Stu
Oct 13 '06 #6
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."

This is the design in question:
[The example is much like the "animal eats food", "lion eats meat", "cow
eats grass" example.]

What you have is a blatant violation of OO principles (Tell Don't Ask, &
LSP) but I'll let the purists decide if that is poor design.

To make the design more OO would require a lot of rework and there
simply isn't enough information provided to decide how that rework
should look. However, when you are writing the code, *you* know which
type of Document you are working with in what parts of the code, and
which type of printer you are working with. So just put that explicitly
in the code and you should be better off.

So, you might end up with something like this:

class TextDocument {
LinePrinter* printer;
};

class Drawing {
Plotter* plotter;
};

The two classes above may, or may not, derive from Document, depending
on how Document is used in the code.

--
There are two things that simply cannot be doubted, logic and perception.
Doubt those, and you no longer*have anyone to discuss your doubts with,
nor any ability to discuss them.
Oct 13 '06 #7

ma*******@googlemail.com wrote:
Is this design well-formed? It contradicts the LSP and Design by
contract anyhow.
Then no. Any time LSP is violated you have a poor design.

Oct 13 '06 #8

ma*******@googlemail.com wrote:
And if I happen to have hundreds of printers? Or if I wanted to add
some, I would have to add methods to each class in the Document
hierarchy. I don't know...
struct Printable
{
virtual void print() = 0;
virtual ~Printable(){ }
}

struct Document : Printable
{
//...Additional interface
virtual void print();
};

struct Drawing : Printable
{
//... Additional interface
};

struct PrinterBase
{
virtual void print( Printable& printable ) = 0;
virtual ~PrinterBase(){ }
};

struct EpsonPrinter : PrinterBase
{
//To Be Implemented...
virtual void print( Printable& printable );
};

//etc...

No problem... Liskov substitutable. <Drawingis not a <Document>

Regards,

Werner

Oct 13 '06 #9

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

Oct 14 '06 #10
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."
[snip].
I think it's the "is an ellipse a kind-of circle" problem
dealt in the FAQ:

http://www.parashift.com/c++-faq-lit....html#faq-21.6

Perhaps both Plotter and Printer should derive from
something fairly abstract like PrintingDevice?

HTH,
- J.
Oct 14 '06 #11
I like your suggested design, Stuart Golodetz. I like Kirit
Sælensminde's suggestion to use mediation classes, too.

I'll sure be using one of these. My decision will depend on complexity
I guess.

Again, thank you very much, folks.

Oct 14 '06 #12

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

6
by: Paddy | last post by:
Hi, I got tripped up on the way eval works with respect to modules and so wrote a test. It seems that a function carries around knowledge of the globals() present when it was defined. (The...
0
by: Justin | last post by:
Hi, First off, I must apologise for cross posting. I am having difficulty creating a pdf document using perl cgi to do substitution for multiline pdf form fields. I created a pdf template/file...
1
by: Jacob Grydholt Jensen | last post by:
I am trying to learn iSQL*Plus for the 1Z0-007 exam. I am having a bit of problems with the substitution variables. My understanding was that they behave as in SQL*Plus, but my experiments beg to...
1
by: kollareddy | last post by:
Hi all, I am new to xml/xsd world. I want to know the differences between complex type and element being abstract and if both can be declared so, in case of substitution goups.Also can xsi:type...
4
by: Don | last post by:
I think "macro substitution" is the correct term for what I want to do, but, to be sure, here is a description of what I'd like to know is possible: I want to be able to create a create an object...
5
by: Murali | last post by:
In Python, dictionaries can have any hashable value as a string. In particular I can say d = {} d = "Right" d = "Wrong" d = "test" In order to print "test" using % substitution I can say
4
by: Ian | last post by:
Hi, Hopefully a simple question but my brain is hurting... I want to make a regex substitution, using search and replace patterns contained in variables. What I want to do is: $f =...
2
by: Bilal | last post by:
Hello, I'm stuck on this problem for quite some time and hope somebody would be able to guide me. Basically, I need to populate a large number of "template" XML files which have all...
2
by: =?Utf-8?B?UGV0ZXI=?= | last post by:
In our application consisting of several custom user controls we want one of these controls to be excluded from the page cache. For this I tried to use the substitution control for output cache...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.