Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Modelling Birds and Elephants

18 views
Skip to first unread message

Richard J. Botting

unread,
Jun 25, 1999, 3:00:00 AM6/25/99
to
A couple of silly challenges for the OO community...
how does your favorite
OO technique handle the following two situations:

(B): All birds fly, all penguins are birds, penguins don't fly.

(E): All elephants like their keepers, unless the keeper is mean in which case
they don't like him, unless the elephant is Clide who likes all the keepers.

They came from a paper on defeasible logic:
(Delgrande98): James P Delgrande(Simon Fraser U, Burnaby, BC,
Canada)<j...@cs.sfu.ca>, On first-order conditional logics, Artif Intell
V105n1-2(Oct 1998)pp105-137 CR9903-0211

rbotting at CSUSB edu
Software Development research and reviews

Stefan Seefeld

unread,
Jun 25, 1999, 3:00:00 AM6/25/99
to
"Richard J. Botting" wrote:
>
> A couple of silly challenges for the OO community...
> how does your favorite
> OO technique handle the following two situations:
>
> (B): All birds fly, all penguins are birds, penguins don't fly.

I wouldn't handle this one since your first axiom is wrong.

> (E): All elephants like their keepers, unless the keeper is mean in which case
> they don't like him, unless the elephant is Clide who likes all the keepers.

Probably I would have classes Elephant and Keeper. To relate both,
I'd use strategies (such as 'like all keepers unless they are mean').

Stefan
_______________________________________________________

Stefan Seefeld
Departement de Physique
Universite de Montreal
email: seef...@magellan.umontreal.ca

_______________________________________________________

...ich hab' noch einen Koffer in Berlin...

Richard MacDonald

unread,
Jun 25, 1999, 3:00:00 AM6/25/99
to
Stefan Seefeld wrote in message <3773F1A2...@magellan.umontreal.ca>...

>"Richard J. Botting" wrote:
>>
>> A couple of silly challenges for the OO community...
>> how does your favorite
>> OO technique handle the following two situations:
>>
>> (B): All birds fly, all penguins are birds, penguins don't fly.
>
>I wouldn't handle this one since your first axiom is wrong.

Ditto.


>
>> (E): All elephants like their keepers, unless the keeper is mean in which
case
>> they don't like him, unless the elephant is Clide who likes all the
keepers.
>
>Probably I would have classes Elephant and Keeper. To relate both,
>I'd use strategies (such as 'like all keepers unless they are mean').
>

I'd call it a Rule rather than a Strategy, so nothing
really needs to *happen*. We just need to model the
situation. If you actually wanted to do something, such as
query a particular situation (e.g., does Clide like keeper Joe),
create a RuleAnalyzer or something.

Ell

unread,
Jun 25, 1999, 3:00:00 AM6/25/99
to
rjbo...@aol.com.not (Richard J. Botting) wrote:

#A couple of silly challenges for the OO community...
#how does your favorite
#OO technique handle the following two situations:

#(B): All birds fly, all penguins are birds, penguins don't fly.

1) Have the method that implements fly() in Penguin, deny it flight. 2) Have
the controller or manager that flies a bird know that Penguins can't.

#(E): All elephants like their keepers, unless the keeper is mean in which case
#they don't like him, unless the elephant is Clide who likes all the keepers.

As above either 1) implement the exceptions in the Elephant like() method or
2) invest a controller/manager class with knowledge of the exceptions.

Also this may be a case where fuzzy logic can play a role.

Elliott

#They came from a paper on defeasible logic:
#(Delgrande98): James P Delgrande(Simon Fraser U, Burnaby, BC,
#Canada)<j...@cs.sfu.ca>, On first-order conditional logics, Artif Intell
#V105n1-2(Oct 1998)pp105-137 CR9903-0211
#
#rbotting at CSUSB edu
#Software Development research and reviews
--
:=***=: Objective * Holistic * Overall pre-code Modelling :=***=:
Hallmarks of the best SW Engineering
study Craftite vs. Full Blown OO: http://www.access.digex.net/~ell
copyright 1999 Elliott. exclusive of others' writing. may be copied freely
only in comp., phil., sci. usenet & bitnet & otug.

Daniel T.

unread,
Jun 28, 1999, 3:00:00 AM6/28/99
to
In article <19990625165705...@ng-ci1.aol.com>,

rjbo...@aol.com.not (Richard J. Botting) wrote:

>A couple of silly challenges for the OO community...

>how does your favorite


>OO technique handle the following two situations:
>

>(B): All birds fly, all penguins are birds, penguins don't fly.

So we have birds and penguins. Bird is a pure abstract class that has a
"MoveTo" method, penguin's "MoveTo" method causes it to walk/swim, other
subclasses of bird have "MoveTo" methods that cause them to fly...

The important point here is that Bird does *not* have a fly method, flying
is an implamentation detail.

>(E): All elephants like their keepers, unless the keeper is mean in which case

>they don't like him, unless the elephant is Clide who likes all the keepers.

Meaness is in the eye of the beholder. The Elephant class maintains a list
of knownKeepers. Inside the Elephant class is a rating on the "meanness"
of each knownKeeper. A particular Elephant object's reaction to a
particular keeper is based on the meaness rating. The rating is adjusted
based on the actions of the keeper and atributes within the elephant.
Clide's attributes are such that all keepers and concidered "not mean".

This method allows each elephant to judge the meanness of each keeper
independantly and mimics real life.

Of course, my answers can only be as serious (or silly) as the challenges. :-)

Richard MacDonald

unread,
Jun 28, 1999, 3:00:00 AM6/28/99
to
Ell wrote in message <37740c32...@news1.radix.net>...

>rjbo...@aol.com.not (Richard J. Botting) wrote:
>
>#A couple of silly challenges for the OO community...
>#how does your favorite
>#OO technique handle the following two situations:
>
>#(B): All birds fly, all penguins are birds, penguins don't fly.

>
>
>Also this may be a case where fuzzy logic can play a role.
>
How? Why?
There is nothing fuzzy about the statement.

Robert C. Martin

unread,
Jun 28, 1999, 3:00:00 AM6/28/99
to

Richard J. Botting wrote in message
<19990625165705...@ng-ci1.aol.com>...

>A couple of silly challenges for the OO community...
>how does your favorite

>OO technique handle the following two situations:
>
>(B): All birds fly, all penguins are birds, penguins don't fly.

OO techniques don't deal with this at all. OO is not a way to state logical
syllogisms (sp?). It is a technique for structuring software.


Robert C. Martin | Design Consulting | Training courses offered:
Object Mentor | rma...@oma.com | Object Oriented Design
14619 N Somerset Cr | Tel: (800) 338-6716 | C++
Green Oaks IL 60048 | Fax: (847) 918-1023 | http://www.oma.com

TINCC.

Robert C. Martin

unread,
Jun 28, 1999, 3:00:00 AM6/28/99
to

>rjbo...@aol.com.not (Richard J. Botting) wrote:
>
>#A couple of silly challenges for the OO community...
>#how does your favorite

>#OO technique handle the following two situations:
>
>#(B): All birds fly, all penguins are birds, penguins don't fly.
>
>1) Have the method that implements fly() in Penguin, deny it flight. 2)
Have
>the controller or manager that flies a bird know that Penguins can't.

Unfortunately this means that everyone who uses the 'bird' class must also
know about penguins, and every other exception. This is pretty ugly. (It
violates the LSP)

There are several approaches that are better. One is simply to keep 'fly'
out of the Bird class, and to create a Flyable mixin that flying birds
implement and that penguins do not:

class Bird:
{
// no fly function
};

class Flyer
{
public:
virtual void Fly() = 0;
};

class Sparrow : public Bird, public Flyer
{
};

class Penguin : public Bird
{
};

Now if you have a bird and you want to call the fly method:

Bird *b = // some function that gives you a bird.
if (Flyer *f = dynamic_cast<Flyer*>(b))
{
f->Fly();
}

Other approaches are to use Visitor, Acyclic Visitor, or Extension Object
design patterns. All of which are basically ways of simulating
dynamic_cast.

univ...@saltmine.radix.net

unread,
Jun 29, 1999, 3:00:00 AM6/29/99
to
Richard MacDonald <macdo...@bv.com> wrote:
> Ell wrote in message <37740c32...@news1.radix.net>...
>>rjbo...@aol.com.not (Richard J. Botting) wrote:
>>
>>#A couple of silly challenges for the OO community...
>>#how does your favorite
>>#OO technique handle the following two situations:
>>
>>#(B): All birds fly, all penguins are birds, penguins don't fly.
>>
>>
>>Also this may be a case where fuzzy logic can play a role.
>>
> How? Why?
> There is nothing fuzzy about the statement.

Penguins might be thought to belong to multiple classes at the same time.
One of which has a fly() base method flies and another which doesn't.

Elliott

univ...@saltmine.radix.net

unread,
Jun 29, 1999, 3:00:00 AM6/29/99
to
Robert C. Martin <rma...@oma.com> wrote:

> Richard J. Botting wrote in message
> <19990625165705...@ng-ci1.aol.com>...

>>A couple of silly challenges for the OO community...

>>how does your favorite


>>OO technique handle the following two situations:

>>(B): All birds fly, all penguins are birds, penguins don't fly.

> OO techniques don't deal with this at all. OO is not a way to state logical


> syllogisms (sp?). It is a technique for structuring software.

Some of us just gave intra-OO solutions. Our solutions were good for
modelling generally as well as software design. While your abilities
were limited ours were not.

Elliott

TICC.

Mark Addleman

unread,
Jun 29, 1999, 3:00:00 AM6/29/99
to

Robert C. Martin <rma...@oma.com> wrote in message
news:ga3e3.1827$el4....@ord-read.news.verio.net...

> Other approaches are to use Visitor, Acyclic Visitor, or Extension Object
> design patterns. All of which are basically ways of simulating
> dynamic_cast.

What is the Extension Object pattern? I haven't heard of it

Robert C. Martin

unread,
Jun 29, 1999, 3:00:00 AM6/29/99
to

Mark Addleman wrote in message <7lbnve$q...@chronicle.concentric.net>...

>
>Robert C. Martin <rma...@oma.com> wrote in message
>news:ga3e3.1827$el4....@ord-read.news.verio.net...
>> Other approaches are to use Visitor, Acyclic Visitor, or Extension Object
>> design patterns. All of which are basically ways of simulating
>> dynamic_cast.
>
>What is the Extension Object pattern? I haven't heard of it


You can read about it in "Pattern Languages of Program Design 3", Martin,
et. al., Addison Wesley, 1997. (See http://www.oma.com favorite books)

The idea is that an object will maintain a list of other objects that
provide special operations upon it. For example, I might have a Window
hierarchy. Some windows hold text, some do not. If I am writing a spelling
checker, I want it to be able to operate upon a Window. If the window has
no text, it should do nothing. If the window has text of some sort, it
should check the spelling.

Since text can be stored in windows in a remarkably different number of
ways, we need some kind of polymorphic mechanism for pulling the text out of
the windows so that the spell checker doesn't have to know about all the
different kinds of windows.

However, I don't want spell check methods polluting the window base class.
So, when the spell checker is given a window, it asks that window for its
text interface. It does this by saying Extension* e =
window.GetExtention("Text");

The window looks in its private map of extention objects to see if it has
one that matches "Text". If so, it returns it. The spell checker then
says:

TextExtension* te = dynamic_cast<TextExtension*>(e);

And them operations upon the TextExtension object to get the text out of the
window.

Clearly, TextExtention is a base class, and there are derivatives for each
kind of window. But SpellChecker does not need to know this. It simply
uses the TextExtention interface.

What's neat about this pattern is that extention objects can be dynamically
loaded into or removed from objects over time. So the dynamic state of the
object can control the interfaces it makes available to the outside world.

Robert C. Martin

unread,
Jun 29, 1999, 3:00:00 AM6/29/99
to

Ell wrote in message <377a631d...@news1.radix.net>...
>"Robert C. Martin" <rma...@oma.com> wrote:

>
>#>rjbo...@aol.com.not (Richard J. Botting) wrote:
>#>
>#>#A couple of silly challenges for the OO community...
>#>#how does your favorite
>#>#OO technique handle the following two situations:
>
># Elliott wrote:
>#>#(B): All birds fly, all penguins are birds, penguins don't fly.
>#>
>#>1) Have the method that implements fly() in Penguin, deny it flight. 2)
>#Have
>#>the controller or manager that flies a bird know that Penguins can't.
>
>#Unfortunately this means that everyone who uses the 'bird' class must also
>#know about penguins, and every other exception. This is pretty ugly. (It
>#violates the LSP)
>
>Check it out. I said the fly() method in Penguin as class. 1) One can have
>define the fly method in the Bird class as 'pure virtual' so that it has
>knowledge of nothing whatsoever and is then defined without flying in its
>implementation in the Penguin class.

Right. And if you do that, then everybody who calls 'Fly' on bird may have
to check to see if the Bird is really a penguin before they call the defunct
'Fly' function. Remember, the functions that call 'Fly' are doing so for a
reason. If the 'Fly' function isn't going to work for Penguins, then those
functions that expect a certain behavior are going to become confused.
Their authors may eventually put 'if' statement in to check to see if the
Bird is really a Penguin. ...And the code will rot.

> 2) Or alternatively, if the fly()
>method in the Bird class is declared 'virtual', it can simply be overridden
>with a fly() method where flight is absent in the Penguin class.

Right, same argument. Barbara Liskov discussed this in 1986. True subtypes
cannot take away functionality. (e.g. deny flight). This is also the crux
of Bertrand Meyer's notion of Design by Contract. Callers of Bird::Fly()
expect something to happen. There is an implied contract that the bird will
take flight. Penguins will break this contract, and confuse the callers.

Thus, it's best not to a function in a base class if some of the derivatives
don't have a decent way to implement it.

Robert C. Martin

unread,
Jun 29, 1999, 3:00:00 AM6/29/99
to

Maxwell Sayles wrote in message <3779910C...@spots.ab.ca>...

>
>
>"Robert C. Martin" wrote:
>
>> Other approaches are to use Visitor, Acyclic Visitor, or Extension Object
>> design patterns. All of which are basically ways of simulating
>> dynamic_cast.
>
>Good example BTW. Could you also give an example for Visitor, Acyclic
>Visitor, and Extension Object? For my own knowledge and others. Much
>appreciated.


You can read about 'Visitor' in the "Design Patterns" book by Gamma.
Acyclic Visitor is documented in the paper by that name in the publications
section of http://www.oma.com. Extension Object is documented in "Pattern
Languages of Program Design 3", Martin, et. al. (See our favorite books
link at http://www.oma.com)

I'll show you the Visitor solution, and let you do the legwork on the
others.

class Bird
{
public:
virtual bool Accept(BirdVisitor& v) = 0;
};

class Sparrow : public Bird
{
public:
virtual bool Accept(BirdVisitor& v)
{return v.Visit(*this);}
};

class Penguin : public Bird
{
public:
virtual bool Accept(BirdVisitor& v)
{return v.Visit(*this);}
};

class BirdVisitor
{
public:
virtual bool Visit(Sparrow&) {return false;}
virtual bool Visit(Penguin&) {return false;}
};

class FlyVisitor : public BirdVisitor
{
public:
virtual void Visit(Sparrow& s)
{FlySparrow(s); return true;}
}

void TryToFlyABird(Bird& b)
{
FlyVisitor v;
if (b.Accept(v))
{
// we flew!!
}
else
{
// no flight.

Ell

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
"Robert C. Martin" <rma...@oma.com> wrote:

#Richard J. Botting wrote in message

#>A couple of silly challenges for the OO community...
#>how does your favorite
#>OO technique handle the following two situations:
#>
#>(B): All birds fly, all penguins are birds, penguins don't fly.

#OO techniques don't deal with this at all. OO is not a way to state logical
#syllogisms (sp?). It is a technique for structuring software.

Why can't the base features of OO modelling paradigm like - abstraction
(data+behavior), polymorphism, state, identity, encapsulation, etc. - be
applied in a mostly consistent manner to various contexts of interests, such
that those contexts are illuminated in way which contributes to our knowledge
and understanding of both software engineering and reality in general?

And many of us indeed do find that the OO modelling paradigm is a powerful
means of gaining insight into, for instance, business re-engineering contexts
apart from a single minded devotion to developing software. And even where it
was used to develop software, the analysis modelling, which also had object
aspects contributed to an understanding of the application domain beyond the
needs of software development.

When did it become honorable, distinguished and wise to take and promulgate a
narrow, mechanical view and approach to understanding and comprehending the
world?

Elliott

Ell

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
"Robert C. Martin" <rma...@oma.com> wrote:

#>rjbo...@aol.com.not (Richard J. Botting) wrote:
#>
#>#A couple of silly challenges for the OO community...
#>#how does your favorite
#>#OO technique handle the following two situations:

# Elliott wrote:
#>#(B): All birds fly, all penguins are birds, penguins don't fly.
#>
#>1) Have the method that implements fly() in Penguin, deny it flight. 2)
#Have
#>the controller or manager that flies a bird know that Penguins can't.

#Unfortunately this means that everyone who uses the 'bird' class must also
#know about penguins, and every other exception. This is pretty ugly. (It
#violates the LSP)

Check it out. I said the fly() method in Penguin as class. 1) One can have
define the fly method in the Bird class as 'pure virtual' so that it has
knowledge of nothing whatsoever and is then defined without flying in its

implementation in the Penguin class. 2) Or alternatively, if the fly()


method in the Bird class is declared 'virtual', it can simply be overridden
with a fly() method where flight is absent in the Penguin class.

#There are several approaches that are better. One is simply to keep 'fly'
#out of the Bird class, and to create a Flyable mixin that flying birds
#implement and that penguins do not:

This is also valid. Many choices and tradeoffs which still reflect domain
semantics are possible. But again I find the 2 approaches I outline above as
acceptable, non-ugly alternatives.

In fact, 2 I outline allow a pointer to the Bird class which knows about a
fly() method in Bird to work. Whereas with your solution we couldn't just
call fly() on any descendant of Bird. We would, in the probably the best
scenario in your mixin case, have to dynamic cast to verify that a descendant
of the Bird class had a fly() method. This in some ways is at least a little
ugly--and really more than a little ugly. My solutions above avoid having to
dynamic cast to implement a dynamic solution.

Maxwell Sayles

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to

"Robert C. Martin" wrote:

> Other approaches are to use Visitor, Acyclic Visitor, or Extension Object
> design patterns. All of which are basically ways of simulating
> dynamic_cast.

Good example BTW. Could you also give an example for Visitor, Acyclic
Visitor, and Extension Object? For my own knowledge and others. Much
appreciated.

Thanks
Maxwell Sayles


Robert O'Dowd

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
univ...@saltmine.radix.net wrote:
>
> Richard MacDonald <macdo...@bv.com> wrote:
> > Ell wrote in message <37740c32...@news1.radix.net>...
> >>rjbo...@aol.com.not (Richard J. Botting) wrote:
> >>
> >>#A couple of silly challenges for the OO community...
> >>#how does your favorite

> >>#OO technique handle the following two situations:
> >>
> >>#(B): All birds fly, all penguins are birds, penguins don't fly.
> >>

OO techniques don't handle this sort of thing per se. OO techniques
provide a toolbox that you use to represent this situation.
Not every representation will work in every application.

However, to answer the intent of the question....

Depending on context, the Bird class would include nothing about
flying. It would include things like characterise all birds,
including penguins and ostriches. This means things like two
legs, feathers (does a penguin have feathers? I'll assume yes
for sake of argument), beak, no teeth,one head, two eyes, etc.

Then I'd have a hierarchy like

Animal
|
Bird
/ \
Flying_bird Grounded_bird


> >>
> >>Also this may be a case where fuzzy logic can play a role.
> >>
> > How? Why?
> > There is nothing fuzzy about the statement.
>
> Penguins might be thought to belong to multiple classes at the same time.
> One of which has a fly() base method flies and another which doesn't.
>

This is another possible solution. Of course, like mine, it's validity
depends on what you want to *do* with the Bird class.

-<Automagically included trailer>
Robert O'Dowd Ph +61 (8) 8259 6546
MOD/DSTO Fax +61 (8) 8259 5139
P.O. Box 1500 Email:
robert...@dsto.defence.gov.au
Salisbury, South Australia, 5108

Disclaimer: Opinions above are mine and may be worth what you paid for
them

Ell

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
"Robert C. Martin" <rma...@oma.com> wrote:

#Elliott wrote:
#>
#>rjbo...@aol.com.not (Richard J. Botting) wrote:
#>#


#>#A couple of silly challenges for the OO community...
#>#how does your favorite

#>#OO technique handle the following two situations:
#>#
#>#(B): All birds fly, all penguins are birds, penguins don't fly.

#>1) Have the method that implements fly() in Penguin, deny it flight. 2)

#>Have
#>the controller or manager that flies a bird know that Penguins can't.

#Unfortunately this means that everyone who uses the 'bird' class must also
#know about penguins, and every other exception. This is pretty ugly. (It
#violates the LSP)

No, just the opposite. Your approach is ugly and mine isn't.

With your approach a client must use dynamic_cast and know about Penguin.

With my approach a client does not have to know about Penguin and use
dynamic_cast.

It is precisely because your approach violates LSP that you must use
dynamic_cast on it.

So who's approach is ugly?

Richard MacDonald

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
univ...@saltmine.radix.net wrote in message
<7lb70i$kv0$1...@news1.Radix.Net>...

>Richard MacDonald <macdo...@bv.com> wrote:
>> Ell wrote in message <37740c32...@news1.radix.net>...
>>>rjbo...@aol.com.not (Richard J. Botting) wrote:
>>>
>>>#A couple of silly challenges for the OO community...
>>>#how does your favorite

>>>#OO technique handle the following two situations:
>>>
>>>#(B): All birds fly, all penguins are birds, penguins don't fly.
>>>
>>>
>>>Also this may be a case where fuzzy logic can play a role.
>>>
>> How? Why?
>> There is nothing fuzzy about the statement.
>
>Penguins might be thought to belong to multiple classes at the same time.
>One of which has a fly() base method flies and another which doesn't.
>
Nothing fuzzy about that statement either.
Unless you mean fuzzy concept, rather than fuzzy logic?

Brad Settlemyer

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
Ell wrote:

> In fact, 2 I outline allow a pointer to the Bird class which knows about a
> fly() method in Bird to work. Whereas with your solution we couldn't just
> call fly() on any descendant of Bird. We would, in the probably the best
> scenario in your mixin case, have to dynamic cast to verify that a descendant
> of the Bird class had a fly() method. This in some ways is at least a little
> ugly--and really more than a little ugly. My solutions above avoid having to
> dynamic cast to implement a dynamic solution.

It doesn't make sense to call fly() on penguins, ostrich's etc, unless
they are a special type that has flight. Your solution allows us to
call fly on every bird which is incorrect if the bird can't fly. That
is like giving an automobile class the fly method, or even a house,
don't let objects have operations they cannot sensibly perform. A
canFly() method (that returned a bool) I would have agreed would work
(although be Kludgy), what about special Penguins or Ostrichs that can
fly. Now your class inconsistency causes a mess, Robert's use of a
"mixin class" still works great.
Flying is not a property of birds and thus should not be included in the
bird class, flying is the property of some birds, but may be distributed
assymetrically about the species and therefore should a mixin class.

Thanks
Brad
>brad.se...@gsc.gte.com<

Brad Settlemyer

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
Ell wrote:

> It is precisely because your approach violates LSP that you must use
> dynamic_cast on it.
>
> So who's approach is ugly?

I don't know about ugly, but your approach is incorrect. You provide
objects operations they cannot possibly perform. To take your approach
to its conclusion, every single possible thing any bird can do should be
in the bird class, and then the subclasses should just make those
methods deny that action. Yes it allows you to treat objects
generically, unfortunately it isn't object oriented. Dynamic casting is
not ugly (other parts of RTTI I am not so sure about, but . . .).

Later
Brad
>brad.se...@gte.gsc.com<

Robert C. Martin

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to

Ell wrote in message <3781bc3f...@news1.radix.net>...

>"Robert C. Martin" <rma...@oma.com> wrote:
>
>#Elliott wrote:
>#>
>#>rjbo...@aol.com.not (Richard J. Botting) wrote:
>#>#
>#>#A couple of silly challenges for the OO community...
>#>#how does your favorite
>#>#OO technique handle the following two situations:
>#>#
>#>#(B): All birds fly, all penguins are birds, penguins don't fly.
>
>#>1) Have the method that implements fly() in Penguin, deny it flight. 2)
>
>#>Have
>#>the controller or manager that flies a bird know that Penguins can't.
>
>#Unfortunately this means that everyone who uses the 'bird' class must also
>#know about penguins, and every other exception. This is pretty ugly. (It
>#violates the LSP)
>
>With your [mixin] approach a client must use dynamic_cast and know about
Penguin.

No, he must know about Bird and Flyer. He could care less about Penguin.
Given a Bird he uses dynamic_cast to see of the bird implements the Flyer
interface. If so, he calls Fly(). Since he wanted to call Fly() in the
first place, the dependency upon Flyer is not harmful.

>With my approach a client does not have to know about Penguin and use
>dynamic_cast.


No, if Bird has a Fly() function, and Penguin implements it to do nothing,
or to emit an error message, then clients who use Bird have to be careful
when they call Fly(). Those that must call Fly() will want to check to see
if the Bird can actually fly before they call Fly(). Without additional
help, this means that they are going to have to use typeid, or dynamic_cast
to see if the Bird they are using is a Penguin. Causing every client of
Fly() to depend upon Penguin.

You might say that this is no worse than a dependency upon Flyer. But it
is. The list of flightless birds is not closed. If I add a new flightless
bird to the hierarchy (e.g. Ostrich or Emu) then I must go back to every
client that checked for Penguin, and add a check for Ostrich. This violates
the Open Closed Principle. (Which is what violations of LSP always
eventually cause)

Someone mentioned the use of a CanFly() method. This solution has a number
of problems. First, it is somewhat error prone. Users might call Fly()
without calling CanFly first. Second, and more important, the list of
CanXXX methods is not closed. Once we start adding them to the base class,
we may find we want more and more and more: CanChirp(), CanSwim(),
CanBuildNest(), CanEatWorms(), etc, etc. This is a pandora's box best left
closed.

Someone else mentioned a deep hiearchy:

|Bird|
A
|
+--------+--------+
| |
|FlyingBird| |FlightlessBird|
A
|
|Penguin|

The problem with this approach is that the the behaviors are birds may not
follow a strict hiearchy. For example, we might want to introduce the
concept of SwimmingBird. Penguins are certainly SwimmingBirds, but then so
are some others that have flight. Do we create FlightlessSwimmingBird and
FlyingSwimmingBird? If so, it begs the question of what to do when we
introduce yet another degree of freedom (e.g. HoppingBird) will we then
have FlightlessSwimmingHoppingBird, FlyingSwimmingHoppingBird,
FlightlessNonSwimmingHoppingBird, etc. etc? Following this would lead to a
geometric explosion of classes. Worse we would have no single base class on
which to polymorphically call Swim or Hop.

The best approach, IMHO, is the mixin approach:

public class Bird {/* lots of stuff about birds but nothing about flying,
swimming, hopping, etc*/}

public interface Flyer {public void Fly();}
public interface Swimmer {public void Swim();}
public interface Hopper {public void Hop();}

public class Penguin extends Bird
implements Swimmer
{
public void Swim() {/* implement swim for Penguin */};
}

Now, given a Bird, I can do the following:

Bird b = /* some bird source */

if (b instanceof Swimmer)
{
Swimmer s = (Swimmer)b;
s.Swim();
}

Only clients of Swim care about Swimmer. Only clients of Fly care about
Flyer.
New capabilities can be added to birds by adding new mixin classes, but the
existing clients who call Fly and Swim will be unaffected. Any bird that
does not have the new capability will also be unaffected. This is pretty
nice.

Daniel T.

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
Dear Robert and Elliott,

I admire both your works and read both your web pages (and books) with
rellish but I don't like either of your solutions in this case.

The origional problem...


> All birds fly, all penguins are birds, penguins don't fly.

The key here is that the first premise is wrong. It is incorrect to model
falty premises in your code therefore "fly" *cannot* be included in
"bird". This means that Elliott's answer is off.

Having a "Flyer" mixin (Robert's idea) is better but there is no end to
the number of mixins that you can have and many of them would in effect do
the exact same thing.

class Flyer, Walker, Swimmer, Hopper, etc...

I think the best idea is to ask, why does a bird fly? What is it trying to
accomplish? Answering these questions and keeping in mind that a method is
a request, not a demand brings us to:

class Bird { public: virtual Location& MoveTo(const Location) = 0;
Location& CurrentLocation() const;
};

The client requests that the bird object move to a particular location,
Penguins will impliment that request by walking (waddling) and swimming,
sparows will impliment it by flying and hopping. The client doesn't have
to care what particular implamentation the bird uses. All the client cares
about is if the bird succeded in getting to the location, or if it failed,
what location it *did* get to as in:

...
Location startingLocation(/*whatever*/);
Bird* b = new /*bird subtype*/(startingLocation);

Location endingLocation(/*whatever*/);

if (b->MoveTo(endingLocation) == endingLocation)
{
// The bird got there
}
else
{
Location where(b->CurrentLocation());
}
...

This of course is just one humble beginners opinion.

Brad Settlemyer

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
Robert C. Martin wrote:


I was actually thinking of the bridge pattern when we were talking about
a "mixin class," wouldn't that be better. Isn't this the exact same
case as socket that was posted several weeks ago? I realize that each
bird MAY fly differently, but that does not have to be the case so I
thought the bridge where you picked your flying class (maybe by giving
it a speed, and a flapping style (eagle vs. hummingbird)?), that way we
can get some reuse. Any thoughts?

Brad
>brad.se...@gte.gsc.com<

Stefan Seefeld

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
Richard MacDonald wrote:

> >> (E): All elephants like their keepers, unless the keeper is mean in which
> case
> >> they don't like him, unless the elephant is Clide who likes all the
> keepers.
> >

> >Probably I would have classes Elephant and Keeper. To relate both,
> >I'd use strategies (such as 'like all keepers unless they are mean').
> >
> I'd call it a Rule rather than a Strategy, so nothing
> really needs to *happen*. We just need to model the
> situation. If you actually wanted to do something, such as
> query a particular situation (e.g., does Clide like keeper Joe),
> create a RuleAnalyzer or something.

I used Strategy in the pattern sense. Attach the behavior to a class
in terms of an aggregation. If for example a keeper changes his attitude
(meanness), an Elephant would probably change his strategy towards him.
Well, it's something in between the Strategy and the State pattern I think...

Harry Chomsky

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
Chris,

I'm glad somebody posted an example of a Dylan-style solution to this
problem. I've been studying Dylan and multi-methods for just a couple of
weeks now and I have some questions that maybe you can answer. Basically,
I'm trying to understand how much the compiler is able to help the
programmer by ensuring he/she doesn't do something stupid.

1) Suppose I mistakenly wrote just "fly(bird)" instead of
"if(try-to-fly(bird)) ..." in the function doit. Would the compiler observe
that the generic function fly is not fully specified, and therefore it's
potentially unsafe to call it with a <bird> argument? Would the compiler
issue an error or a warning (or neither)? In particular, what if bird was
created as a <penguin> instead of as a <sparrow>? Would I have to wait
until run-time to discover my error?

2) Suppose I define two new types of bird:

define class <robin> (<bird>) end;
define class <ostrich> (<bird>) end;

Am I responsible for providing specializations of fly and/or try-to-fly for
<robin> and <ostrich>? Obviously I can't write a fly method for <ostrich>.
Do I need to write an explicit try-to-fly method for <ostrich> returning #f?
And for <robin>, do I need to write both methods? Is there any way to avoid
all this duplication? What happens if I neglect to write some of the
specializations that are required? What happens if I make a mistake and
write the following:

define method try-to-fly (o :: <ostrich>) => (flew? :: <boolean>)
fly(o);
#t;
end method try-to-fly;

In traditional OO languages like Eiffel and Java, there are mechanisms to
help with this kind of problem -- deferred (abstract) methods which must be
implemented in subclasses, mixin classes (interfaces), assignment attempt
(instanceof). See Robert Martin's message in this thread for a pretty safe
way to handle this in Java. By "safe" I mean that it's likely mistakes will
be caught at compile-time instead of run-time. I'm interested in Dylan and
I like the idea of multi-methods, but I can't live without this sort of
safety. I hope you can convince me that I can learn to use multi-methods
safely!

TIA

Chris Double wrote in message ...
> define abstract class <bird> (<object>) end;
> define class <sparrow> (<bird>) end;
> define class <penguin> (<bird>) end;
>
> define generic fly( b :: <bird> ) => ();
> define generic try-to-fly( b :: <bird> ) => (flew? :: <boolean>);
>
> define method fly( s :: <sparrow> ) => ()
> format-out("Sparrow flying!");
> end method fly;
>
> define method try-to-fly(s :: <sparrow>) => (flew? :: <boolean>)
> fly(s);
> #t;
> end method try-to-fly;
>
> define method try-to-fly(b :: <penguin>) => (flew? :: <boolean>)
> #f;
> end method try-to-fly;
>
> define function do-it()
> let bird = make(<sparrow>);
> if(try-to-fly(bird))
> format-out("I flew.\n");
> else
> format-out("I didn't fly.\n");
> end if;
> end function do-it;


Jim Cochrane

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
(Sorry for butting in here.) I think this is a good solution. I was going
to bring it up myself - using the MoveTo method you brought up earlier (I
assume it was you), but you beat me to it, which is appropriate, since you
originated the idea in this thread.

A further elaboration could be (assuming the resulting behavior is
compatible with the requirements, whatever they are) to include the
type of terrain the bird is in/on as its current state. When MoveTo is
called, the "bird" would, based on the type of terrain it is in and how
far it needs to move, choose the appropriate method for moving - flying,
swimming, walking, etc.

An even further elaboration would allow dynamically configuring whether a
particular animal (let's assume here that it is appropriate to include
movement of any animal in this package) can fly, walk, etc. Each
animal would have a Transporter for each type of terrain it needs to
move in. A builder could be used to construct animals and associate the
appropriate types of transporters to each animal. A penguin could get a
SwimTransporter for water terrain and a WalkTransporter for ground terrain.
The animal class's move method could then perhaps simply be implemented
as something like:

MoveTo(Location l) {
transporters[CurrentTerrainType].Move(CurrentLocation, l)
}

Thus, when the terrain type is land, a penguin would end up using, via
dynamic binding, a WalkTransporter. A sparrow - if `l' is very far from
the current location, would end up using a FlightTranporter.

(Moving from one terrain type to a different terrain type would complicate
this a little bit.) With this scheme, focusing on just the requirement for
movement, a class hierarchy for animals is not needed. Instead, a class
hierarchy would be needed with Transporter as the root.

In article <daniel_t-300...@200.0.0.40>,


--
Jim Cochrane
j...@dimensional.com

Marco Dalla Gasperina

unread,
Jun 30, 1999, 3:00:00 AM6/30/99
to
brou...@yahoo.com wrote:

> void foo(Bird& b)
> {
> try
> {
> b.fly();
> }
> catch (flight_exception& e)
> {
> // this bird can't fly. Or maybe it has a broken wing at the moment.
> }
> }

I'm not Robert and I don't play one on TV. But, the problem may
be that ostrich was added later... after a whole bunch of code
which already assumes birds can fly has been written and tested.
In this case (effectively a CAT call) you are not at liberty to
find all the code code which calls fly() and modify it, paying
ever-so-close attention to not breaking any other post-conditions,
pre-conditions or invariants long the way.

marco


Chris Double

unread,
Jul 1, 1999, 3:00:00 AM7/1/99
to
"Robert C. Martin" <rma...@oma.com> writes:

>
> I'll show you the Visitor solution, and let you do the legwork on the
> others.
>

[...visitor solution snipped...]
>

I quite like the way the visitor solution isolates the changes
required to the visitor classes only if different birds are
added. Here is a similar solution using an OO language where the
protocol is defined using generic functions, with methods added to
those generic functions (In this case the language is Dylan, but it
could just as easily be CLOS or others):

define abstract class <bird> (<object>) end;
define class <sparrow> (<bird>) end;
define class <penguin> (<bird>) end;

define generic fly( b :: <bird> ) => ();
define generic try-to-fly( b :: <bird> ) => (flew? :: <boolean>);

define method fly( s :: <sparrow> ) => ()
format-out("Sparrow flying!");
end method fly;

define method try-to-fly(s :: <sparrow>) => (flew? :: <boolean>)
fly(s);
#t;
end method try-to-fly;

define method try-to-fly(b :: <penguin>) => (flew? :: <boolean>)
#f;
end method try-to-fly;

define function do-it()
let bird = make(<sparrow>);
if(try-to-fly(bird))
format-out("I flew.\n");
else
format-out("I didn't fly.\n");
end if;
end function do-it;

Like the visitor example, all checking for fly-ability is basically
done through method dispatch rather than direct RTTI usage. Unlike
the C++ visitor solution no classes need to be re-opened to tell the
system about a new bird that can or can't fly.

The visitor pattern could be used as well but I don't think it adds
anything except complicating the issue. What are the advantages of
using visitor over the approach above? For reference a direct
translation of the visitor pattern in Dylan could be:

define abstract class <bird> (<object>) end;
define class <sparrow> (<bird>) end;
define class <penguin> (<bird>) end;

define class <bird-visitor> (<object>) end;
defnie class <fly-visitor> (<bird-visitor>) end;

define generic fly-sparrow( s :: <sparrow> ) => ();
define generic accept( b :: <bird>, v :: <object> )
=> (flew? :: <boolean>);
define generic visit( visitor, object ) => (visited? :: <boolean>);

define method accept( b :: <bird>, v :: <object> )
=> (flew? :: <boolean>)
visit(v, b);
end method accept;

define method visit( v :: <bird-visitor>, b :: <sparrow>)
=> (v? :: <boolean>)
#f;
end method visit;

define method visit( v :: <bird-visitor>, b :: <penguin>)
=> (v? :: <boolean>)
#f;
end method visit;

define method visit( v :: <fly-visitor>, b :: <sparrow>)
=> (v? :: <boolean>)
fly-sparrow(b);
#t;
end method visit;

define method try-to-fly(b :: <bird>) => ()
let visitor = make(<fly-visitor>);
if(visit(b, visitor))


format-out("I flew.\n");
else
format-out("I didn't fly.\n");
end if;

end method try-to-fly;

Chris.

Robert C. Martin

unread,
Jul 1, 1999, 3:00:00 AM7/1/99
to

Daniel T. wrote in message ...

>
>The origional problem...
>> All birds fly, all penguins are birds, penguins don't fly.
>
>The key here is that the first premise is wrong.

Granted.

>It is incorrect to model
>falty premises in your code therefore "fly" *cannot* be included in
>"bird". This means that Elliott's answer is off.
>
>Having a "Flyer" mixin (Robert's idea) is better but there is no end to
>the number of mixins that you can have and many of them would in effect do
>the exact same thing.
>
>class Flyer, Walker, Swimmer, Hopper, etc...
>
>I think the best idea is to ask, why does a bird fly? What is it trying to
>accomplish? Answering these questions and keeping in mind that a method is
>a request, not a demand brings us to:
>
>class Bird { public: virtual Location& MoveTo(const Location) = 0;
> Location& CurrentLocation() const;
> };

I agree that this is the proper way to think about the problem. However, I
disagree that all problems can be solved this way. The Bird/Penguin
proposition is an analogy to things that really do occurr in software. i.e.
it represent the notion that there are hierarchies in which most derivatives
have function 'f', but a minority of derivatives don't have any good
implementation for 'f'. Your solution assumes that dilemma can always be
resolved by the use of abstract thinking. Unfortunately, it cannot. The
Bird/Penguin dilemma really exists.

Consider, for example, class Modem:

class Modem
{
public:
virtual void Dial(char*) = 0;
virtual void Hangup() = 0;
virtual void Send(char) = 0;
virtual char Recv() = 0;
};

Presume that hundreds of applications use the class. And that there are
many different derivatives of it.

Now presume that a new kind of modem appears (actually a very old kind of
modem). A Cable modem. One that does not need to be dialled or hung up.

We'd like all the programs that currently use Modem to be able to use
CableModem, which means that CableModem must be a derivative of Modem. But
CableModem does not have a reasonable implementation for Dial or Hangup.

Proper abstract thinking might suggest that you alter the Modem hiearchy as
follows:

class Modem
{
public:
virtual void Send(char) = 0;
virtual char Recv() = 0;
};

class DiallingModem : public Modem
{
public:
virtual void Dial(char*) = 0;
virtual void Hangup() = 0;
virtual void Send(char) = 0;
virtual char Recv() = 0;
}

Then CableModem can derive from Modem and won't have to worry about Dial and
Hangup. But if you do this, then all the applications that currently use
modem must be recompiled and retested. This may be to great a price to pay
for "correct abstraction".

In the end, software is engineering, and engineering is sometimes ugly.
There are no perfect foolproof solutions to all problems.


Robert C. Martin | Design Consulting | Training courses offered:
Object Mentor | rma...@oma.com | Object Oriented Design
14619 N Somerset Cr | Tel: (800) 338-6716 | C++
Green Oaks IL 60048 | Fax: (847) 918-1023 | http://www.oma.com

TINCC.

>

Robert C. Martin

unread,
Jul 1, 1999, 3:00:00 AM7/1/99
to

Brad Settlemyer wrote in message <377A3732...@nowhere.com>...

>I was actually thinking of the bridge pattern when we were talking about
>a "mixin class," wouldn't that be better. Isn't this the exact same
>case as socket that was posted several weeks ago? I realize that each
>bird MAY fly differently, but that does not have to be the case so I
>thought the bridge where you picked your flying class (maybe by giving
>it a speed, and a flapping style (eagle vs. hummingbird)?), that way we
>can get some reuse. Any thoughts?


I guess I'd need to see an example. I'm not sure how Bridge fits in this
case.

Robert C. Martin

unread,
Jul 1, 1999, 3:00:00 AM7/1/99
to

brou...@yahoo.com wrote in message <377a8c53...@news.newsguy.com>...

>"Robert C. Martin" <rma...@oma.com> wrote:
>
>>Someone mentioned the use of a CanFly() method. This solution has a
number
>>of problems. First, it is somewhat error prone. Users might call Fly()
>>without calling CanFly first.
>
>I don't disagree with your analysis, Robert. I'd probably use a mixin
class
>or something akin to the visitor. But I thought I'd toss this out, just
for
>discussion.
>
>Your thoughts?

>
>void foo(Bird& b)
>{
> try
> {
> b.fly();
> }
> catch (flight_exception& e)
> {
> // this bird can't fly. Or maybe it has a broken wing at the moment.
> }
>}
>

This presumes that the users of the Fly function expect exceptions to be
thrown in the event of problems. However, if the base class of Bird has a
Fly method, and no thought was given to Penguins when it was written, then
it is very unlikely that any clients would put calls to Fly in a try/catch
block. Later, when Penguin was added, all the clients would have to be
altered...

The situation in Java is worse, since in Java the Fly method in the Bird
class must needs declare the exception to be thrown. It is unlikely that
the author of Bird would have declared Fly to thrown an exception. So when
Penguin is added, the base class Bird must be changed, along with all
clients of Bird.

Peter

unread,
Jul 1, 1999, 3:00:00 AM7/1/99
to
> rjbo...@aol.com.not (Richard J. Botting) wrote:
>
> #A couple of silly challenges for the OO community...
> #how does your favorite

> #OO technique handle the following two situations:
>
> #(B): All birds fly, all penguins are birds, penguins don't fly.
>

The original statements aren't consistent, but here are two ways to model
it (in Dylan):

define class <bird> (<animal>)
end;

define class <penguin> (<bird>)
end;

define method fly (bird :: <bird>)
flap(bird.wings);
end;

define method fly (penguin:: <penguin>)
error("Don't you know that penguins can't fly, silly?");
end;


--------- or using multiple inheritance --------------
define abstract class <bird> (<animal>)
end;

define abstract class <flying-animal> (<animal-behavour-mixin>)
end;

define class <flying-bird> (<bird, <flying-animal>)
end;

define class <penguin> (<bird>)
end;

define method fly (flying-bird:: <flying-bird>)
flap(flying-bird.wings);
end;


>
> #(E): All elephants like their keepers, unless the keeper is mean in
which case
> #they don't like him, unless the elephant is Clide who likes all the keepers.
>

This second example shows off the power of Dylan better. In this case, I
use the singleton dispatch capabilities of Dylan to specify unique
behaviour for Clide.

define class <elephant> (<animal>)
slot keeper :: <keeper>;
end;

define class <keeper> (<person>)
slot is-mean? :: <boolean>;
end;

def
define method like-keeper? (elephant :: <elephant>)
~elephant.keeper.is-mean?;
end;

define method like-keeper? (elephant == $Clide)
#t;
end;

Peter

unread,
Jul 1, 1999, 3:00:00 AM7/1/99
to
In article <sIxe3.554$mq2....@typhoon-sf.snfc21.pbi.net>, "Harry
Chomsky" <har...@chomsky.net> wrote:

> Chris,
>
> I'm glad somebody posted an example of a Dylan-style solution to this
> problem. I've been studying Dylan and multi-methods for just a couple of
> weeks now and I have some questions that maybe you can answer. Basically,
> I'm trying to understand how much the compiler is able to help the
> programmer by ensuring he/she doesn't do something stupid.
>
> 1) Suppose I mistakenly wrote just "fly(bird)" instead of
> "if(try-to-fly(bird)) ..." in the function doit. Would the compiler observe
> that the generic function fly is not fully specified, and therefore it's
> potentially unsafe to call it with a <bird> argument? Would the compiler
> issue an error or a warning (or neither)? In particular, what if bird was
> created as a <penguin> instead of as a <sparrow>? Would I have to wait
> until run-time to discover my error?

As long as the generic function is not declared as being 'open', the
compiler will notice that no applicable method exists, and will generate
an error at compile time.

If the function is open (at least over that domain), then the compiler
can't prove anything is wrong, and it won't say anything.


>
> 2) Suppose I define two new types of bird:
>
> define class <robin> (<bird>) end;
> define class <ostrich> (<bird>) end;
>
> Am I responsible for providing specializations of fly and/or try-to-fly for
> <robin> and <ostrich>?

Only if you want robins and ostriches to have different behavour than the
parent class.

Obviously I can't write a fly method for <ostrich>.
> Do I need to write an explicit try-to-fly method for <ostrich> returning #f?
> And for <robin>, do I need to write both methods? Is there any way to avoid
> all this duplication? What happens if I neglect to write some of the
> specializations that are required? What happens if I make a mistake and
> write the following:
>
> define method try-to-fly (o :: <ostrich>) => (flew? :: <boolean>)
> fly(o);
> #t;
> end method try-to-fly;
>

As long as 'fly' is not an open generic function, then the compiler will
notice that there is no applicable fly method for ostriches, and will give
an error at compile time.

Kevin Kelley

unread,
Jul 1, 1999, 3:00:00 AM7/1/99
to
"Robert C. Martin" <rma...@oma.com> wrote:

> The situation in Java is worse, since in Java the Fly method in the Bird
> class must needs declare the exception to be thrown. It is unlikely that
> the author of Bird would have declared Fly to thrown an exception. So when
> Penguin is added, the base class Bird must be changed, along with all
> clients of Bird.

...or better, in a sense, since now that you've defined Ostrich.fly()
to throw an exception, the compiler will tell you "exception NoCanFly
is neither caught nor declared"; so you add the declaration to Bird.fly()
and recompile, and get an error at each point Bird.fly() is called,
telling you there's an uncaught exception.

I agree that the design isn't that great, since the change requires
modification throughout your code, but at least the compiler is
enforcing it, so you can't overlook it somewhere.


I guess really, the design is weak for having put fly() in Bird
when the problem space could grow to include ostriches and penguins.
It would be nice to be able to foresee this kind of extension
always, and design upfront to handle it, but proper design is
hard, and nearly any design will be brittle for some type of
change. Sometimes the easiest thing to do is to put in "throws
ICannotFly" and let the compiler take you around to the places
you'll have to fix.

This won't work if you're not in control of the full codebase;
if you're writing an extension that gets added to an already-
deployed system, then you can't necessarily modify that system
to expect exceptions on Bird.fly(). In that case, I'm not
sure what you do, except maybe decide that ostriches aren't
really birds after all. If there's something above Bird in
the taxonomy, you can decide that 'Bird' means 'FlyingBird'
and add a sibling class 'NonFlyingBird' for the ostriches
and penguins, but it doesn't seem likely that would buy you
much, since the code that manages Bird instances probably
deals with them as Bird and not as instances of the more
abstract class.


Kevin Kelley

Nikolai Pretzell

unread,
Jul 1, 1999, 3:00:00 AM7/1/99
to


Wouldn't be the (in many cases) best practical solution to this, to
implement Dial() and Hangup() in class CableModem nevertheless, but as
just doing nothing?

Would there be any tradeoff?

I think this resembles the „real life situation“: if you have 5 kinds
of birds, one of which is a Penguin and request each of them to fly,
that is exactly what would happen: The other 4 would fly and the
Penguin would do nothing. So you don't need an exception, also you
don't need make sure, that Penguins can not be requested to fly ( say
have no Fly()-method ). The same with modems.

This would not violate the LSP, because you can request Fly() from a
Penguin like all other birds. He just would not move. A flight of zero
is a flight as well in a mathematical sense, like an empty set is a
set as well.

Or do I overlook anything?

Nikolai
--
Nikolai Pretzell
StarDivision
Sachsenfeld 4, D-20097 Hamburg eMail: n...@stardivision.de


Harry Chomsky

unread,
Jul 1, 1999, 3:00:00 AM7/1/99
to
Peter <0...@0.com> wrote in message <0-0107991...@rac7-13.avana.net>...

>--------- or using multiple inheritance --------------
>define abstract class <bird> (<animal>)
>end;
>
>define abstract class <flying-animal> (<animal-behavour-mixin>)
>end;
>
>define class <flying-bird> (<bird, <flying-animal>)
>end;
>
>define class <penguin> (<bird>)
>end;
>

>define method fly (flying-bird:: <flying-bird>)
> flap(flying-bird.wings);
>end;

This seems very similar to the Java solution that Robert Martin posted (with
"public interface Flyer", etc.). His example went further by showing how
you could take a bird, find out whether it supports flying or not, then tell
it to fly if it does. How would you do that in Dylan? That is, how would I
write the bracketed portions of the function move below?

define method move (bird :: <bird>)
if ([bird conforms to type <flying-bird>])
fly ([bird, or some sort of "cast" of bird to type <flying-bird>]);
else
walk (bird);
end if;
end method move;

Will the compiler prevent me from erroneously writing the following?

define method move (bird :: <bird>)
fly (bird);
end method move;

Sorry for all the newbie Dylan questions. I haven't received my copy of
"Dylan Programming" yet, so for now I'm just trying to understand the
language by using on-line tutorials and the reference manual. There are
some basic OO techniques like this that I still haven't figured out how to
do. Feel free to tell me to RTFM :-). But I hope others reading this
thread will be interested to see how well a multi-method language can handle
these problems.

>This second example shows off the power of Dylan better. In this case, I
>use the singleton dispatch capabilities of Dylan to specify unique
>behaviour for Clide.

[snip sample code]

Nice!


Robert C. Martin

unread,
Jul 1, 1999, 3:00:00 AM7/1/99
to

Nikolai Pretzell wrote in message <19990701...@np-1105.stardiv.de>...

>Wouldn't be the (in many cases) best practical solution to this, to
>implement Dial() and Hangup() in class CableModem nevertheless, but as
>just doing nothing?

No, there is a good chance that the applications that use Modem were written
to expect that no characters would flow from the modems as long as they
weren't dialled. The CableModem *will* send characters before Dial is
called, and may crash those programs.

>I think this resembles the „real life situation“: if you have 5 kinds
>of birds, one of which is a Penguin and request each of them to fly,
>that is exactly what would happen: The other 4 would fly and the
>Penguin would do nothing. So you don't need an exception, also you
>don't need make sure, that Penguins can not be requested to fly ( say
>have no Fly()-method ). The same with modems.

No, the callers of Fly() expect something to happen. If nothing happens
because they happen to call Fly() on a Penguin, they we be confused, and
possibly crash. To defend themselves from these crashes, the programmers
will change their code to check to see if the Birds are Penguins before
calling Fly().

>This would not violate the LSP, because you can request Fly() from a
>Penguin like all other birds. He just would not move. A flight of zero
>is a flight as well in a mathematical sense, like an empty set is a
>set as well.

This is only true if the users of 'Fly()' expect the empty set as one of the
possible options. However, this is not very likely. Especially if Penguin
is added as an afterthought.

Robert C. Martin

unread,
Jul 1, 1999, 3:00:00 AM7/1/99
to

brou...@yahoo.com wrote in message <377c4338...@news.newsguy.com>...

>By the same token, I see nothing wrong with going back and refactoring old
>code when we realize that we've made a mistake. As long as we have a
>complete series of regression tests (unfortunately, not true often enough),
>we can usually assure ourselves that we're not breaking anything major. A
>little pain now might be better than continuous pain for the rest of the
>product's lifecycle.

If, however, there are hundreds of other applications that depend upon the
Bird class, refactoring it is likely to be impractical.

Phlip

unread,
Jul 1, 1999, 3:00:00 AM7/1/99
to
Robert C. Martin wrote:

>No, the callers of Fly() expect something to happen. If nothing
happens
>because they happen to call Fly() on a Penguin, they we be
confused, and
>possibly crash. To defend themselves from these crashes, the
programmers
>will change their code to check to see if the Birds are Penguins
before
>calling Fly().

Scenario A: You scream at the penguin "FLY FLY FLY!" The terrified
animal makes a short hop away from you. This registers as flight
with a non-zero distance, and it satisfies your interface contract.

Scenario B: Remember the episode of The Simpsons where Bart
discovered a comet was headed directly towards Moe's Tavern?...

--
Phlip at politizen dot com (address munged)
======= http://users.deltanet.com/~tegan/home.html =======
-- Can we discuss kiwis next? --

Ell

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
kel...@ruralnet.net (Kevin Kelley) wrote:

#"Robert C. Martin" <rma...@oma.com> wrote:
#
#> The situation in Java is worse, since in Java the Fly method in the Bird
#> class must needs declare the exception to be thrown. It is unlikely that
#> the author of Bird would have declared Fly to thrown an exception. So when
#> Penguin is added, the base class Bird must be changed, along with all
#> clients of Bird.
#
#...or better, in a sense, since now that you've defined Ostrich.fly()
#to throw an exception, the compiler will tell you "exception NoCanFly
#is neither caught nor declared"; so you add the declaration to Bird.fly()
#and recompile, and get an error at each point Bird.fly() is called,
#telling you there's an uncaught exception.
#
#I agree that the design isn't that great, since the change requires
#modification throughout your code, but at least the compiler is
#enforcing it, so you can't overlook it somewhere.
#
#
#I guess really, the design is weak for having put fly() in Bird
#when the problem space could grow to include ostriches and penguins.
#It would be nice to be able to foresee this kind of extension
#always, and design upfront to handle it, but proper design is
#hard, and nearly any design will be brittle for some type of
#change. Sometimes the easiest thing to do is to put in "throws
#ICannotFly" and let the compiler take you around to the places
#you'll have to fix.

And it's easy to simply define the method for fly() in eg Penguin to not fly.
But overall, if non-flying birds are going to be included, or anticipated to
be included, then just implement the less definitive move() method for all
birds.

Elliott

#This won't work if you're not in control of the full codebase;
#if you're writing an extension that gets added to an already-
#deployed system, then you can't necessarily modify that system
#to expect exceptions on Bird.fly(). In that case, I'm not
#sure what you do, except maybe decide that ostriches aren't
#really birds after all. If there's something above Bird in
#the taxonomy, you can decide that 'Bird' means 'FlyingBird'
#and add a sibling class 'NonFlyingBird' for the ostriches
#and penguins, but it doesn't seem likely that would buy you
#much, since the code that manages Bird instances probably
#deals with them as Bird and not as instances of the more
#abstract class.
#
#
#Kevin Kelley

Ell

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
Nikolai Pretzell <n...@stardivision.de> wrote:

#
#> The Bird/Penguin
#> proposition is an analogy to things that really do occurr in software.
# i.e.
#> it represent the notion that there are hierarchies in which most
#derivatives
#> have function 'f', but a minority of derivatives don't have any good
#> implementation for 'f'. Your solution assumes that dilemma can always
#be
#> resolved by the use of abstract thinking. Unfortunately, it cannot.
#The
#> Bird/Penguin dilemma really exists.

RCM:
#> Consider, for example, class Modem:
#
#> class Modem
#> {
#> public:
#> virtual void Dial(char*) = 0;
#> virtual void Hangup() = 0;
#> virtual void Send(char) = 0;
#> virtual char Recv() = 0;
#> };
#
#> Presume that hundreds of applications use the class. And that there
#are
#> many different derivatives of it.
#
#> Now presume that a new kind of modem appears (actually a very old kind
#of
#> modem). A Cable modem. One that does not need to be dialled or hung
#up.
#
#> We'd like all the programs that currently use Modem to be able to use
#> CableModem, which means that CableModem must be a derivative of Modem.
# But
#> CableModem does not have a reasonable implementation for Dial or
#Hangup.
#
#> Proper abstract thinking might suggest that you alter the Modem
#hiearchy as
#> follows:
#
#> class Modem
#> {
#> public:
#> virtual void Send(char) = 0;
#> virtual char Recv() = 0;
#> };
#
#> class DiallingModem : public Modem
#> {
#> public:
#> virtual void Dial(char*) = 0;
#> virtual void Hangup() = 0;
#> virtual void Send(char) = 0;
#> virtual char Recv() = 0;
#> }
#
#> Then CableModem can derive from Modem and won't have to worry about
#Dial and
#> Hangup. But if you do this, then all the applications that currently
#use
#> modem must be recompiled and retested. This may be to great a price
#to pay
#> for "correct abstraction".
#
#> In the end, software is engineering, and engineering is sometimes
#ugly.
#> There are no perfect foolproof solutions to all problems.

There are tradeoffs, some uglier than others. But in many cases we can do as
Pretzell says:

#Wouldn't be the (in many cases) best practical solution to this, to
#implement Dial() and Hangup() in class CableModem nevertheless, but as
#just doing nothing?
#
#Would there be any tradeoff?
#
#I think this resembles the „real life situation“: if you have 5 kinds
#of birds, one of which is a Penguin and request each of them to fly,
#that is exactly what would happen: The other 4 would fly and the
#Penguin would do nothing. So you don't need an exception, also you
#don't need make sure, that Penguins can not be requested to fly ( say
#have no Fly()-method ). The same with modems.
#
#This would not violate the LSP, because you can request Fly() from a
#Penguin like all other birds. He just would not move. A flight of zero
#is a flight as well in a mathematical sense, like an empty set is a
#set as well.
#
#Or do I overlook anything?
#
#Nikolai

I think it makes great sense. No checking necessary, Penguin just doesn't
fly. But it is a valid member of the hierarchy because it so bird like in
most other respects.

Elliott

Robert O'Dowd

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
Robert C. Martin wrote:
>
> Daniel T. wrote in message ...
> >
> >The origional problem...
> >> All birds fly, all penguins are birds, penguins don't fly.
> >
> >The key here is that the first premise is wrong.
>
> Granted.
>
> >It is incorrect to model
> >falty premises in your code therefore "fly" *cannot* be included in
> >"bird". This means that Elliott's answer is off.

[Comments about mixins snipped].

>
> I agree that this is the proper way to think about the problem. However, I
> disagree that all problems can be solved this way. The Bird/Penguin

> proposition is an analogy to things that really do occurr in software. i.e.
> it represent the notion that there are hierarchies in which most derivatives


> have function 'f', but a minority of derivatives don't have any good

> implementation for 'f'. Your solution assumes that dilemma can always be
> resolved by the use of abstract thinking. Unfortunately, it cannot. The
> Bird/Penguin dilemma really exists.
>

> Consider, for example, class Modem:
>

> class Modem
> {
> public:


> virtual void Dial(char*) = 0;

> virtual void Hangup() = 0;

> virtual void Send(char) = 0;

> virtual char Recv() = 0;

> };
>
> Presume that hundreds of applications use the class. And that there are


> many different derivatives of it.
>

> Now presume that a new kind of modem appears (actually a very old kind of
> modem). A Cable modem. One that does not need to be dialled or hung up.


>
> We'd like all the programs that currently use Modem to be able to use

> CableModem, which means that CableModem must be a derivative of Modem. But
> CableModem does not have a reasonable implementation for Dial or Hangup.

You were on a roll, but unfortunately picked a bad example.

A reasonable implementation for Dial() and Hangup() is to do nothing.
That would meet your requirement, and not break any code that only knows
about the Modem class.

The question with Bird can fly, Penguin is a bird that can not really
brings
up a question of what we do when we've analysed a problem, created a
model, implemented it and then an example comes along that breaks our
model.

Clearly this sort of occurrence is a sign that our model is broken.
The ideal solution is to redesign and reimplement the whole thing,
possibly
reusing key bits of code. If that isn't allowed, other pragmatic
solutions may be to

a) Say, for the sake of our model, that a Penguin is not a bird.
Say it is an animal, that just happens to behave like a bird that
can't fly. Remember our model is a representation that helps
us get a job done.

b) Turn Penguin's Fly() method into a no-op. After all, even if you
tell a penguin to fly (and it understands you), it probably
won't fly.

BTW: If I'm told to fly, I'll book a ticket on an aircraft and
use it to fly. This means I can fly. It doesn't mean I'm a bird.
If it's important for solving a problem, however, I'll allow
people to model me as a bird. They'd better document the reasons
for that decision though!

-<Automagically included trailer>
Robert O'Dowd Ph +61 (8) 8259 6546
MOD/DSTO Fax +61 (8) 8259 5139
P.O. Box 1500 Email:
robert...@dsto.defence.gov.au
Salisbury, South Australia, 5108

Disclaimer: Opinions above are mine and may be worth what you paid for
them

Robert O'Dowd

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
Robert C. Martin wrote:
>
> Nikolai Pretzell wrote in message <19990701...@np-1105.stardiv.de>...
>
> >Wouldn't be the (in many cases) best practical solution to this, to
> >implement Dial() and Hangup() in class CableModem nevertheless, but as
> >just doing nothing?
>
> No, there is a good chance that the applications that use Modem were written
> to expect that no characters would flow from the modems as long as they
> weren't dialled. The CableModem *will* send characters before Dial is
> called, and may crash those programs.

My understanding is that Dial and Hangup of modems is achieved by
squirting
control characters at the modem. But never mind....

You've also changed the ground rules on me. No matter.

If applications assume that the modems won't send characters before
Dial()
is invoked, then the CableModem implementation needs to include some
intelligence to mimic that behaviour. The simplest way I can think of
is creation of a class-specific data cache that catches any data
received
from the modem before Dial() is called. The Dial() method, instead
of being a no-op, would allow data from that cache to become available.

>
> >I think this resembles the „real life situation“: if you have 5 kinds

> >of birds, one of which is a Penguin and request each of them to fly,

> >that is exactly what would happen: The other 4 would fly and the

> >Penguin would do nothing. So you don't need an exception, also you

> >don't need make sure, that Penguins can not be requested to fly ( say

> >have no Fly()-method ). The same with modems.
>

> No, the callers of Fly() expect something to happen. If nothing happens
> because they happen to call Fly() on a Penguin, they we be confused, and
> possibly crash. To defend themselves from these crashes, the programmers
> will change their code to check to see if the Birds are Penguins before
> calling Fly().

This is only necessary if you do not use encapsulation properly in the
Bird class.

A basic tenet of encapsulation is that invoking a method may have an
effect,
but there is no specific requirement that it do so. For example,
if a bird is told to fly, it may elect not to. This means that users
of the Bird class must allow for the possibility that the Fly()
method must do nothing. Otherwise you are breaking encapsulation
of the Bird class, and allowing an object to get into an invalid
state.

Also, by encapsulation, the Bird is responsible for maintaining it's own
state. This means, before Fly() is invoked, that any bird must be
in some valid state (be it hopping, pecking, or whatever).
If flying would put a bird into an invalid state, (which would be
the case for a flying penguin) it must refuse to fly.
So, the Fly() method is responsible for ensuring that the bird
is still in a realisable state.

If an application can be adversely affected by the Bird class
refusing to do something then (frankly) that is a problem
for that application to resolve. The only contract that
the Bird class can offer is to be in one of a defined
set of states.

If a Bird enters an invalid state, then every application will
be affected. By definition, if

1) a bird is in a valid state (whatever that may be)
2) bird is told to Fly()
3) bird refuses to Fly()

then that bird is still in a valid state.

>
> >This would not violate the LSP, because you can request Fly() from a

> >Penguin like all other birds. He just would not move. A flight of zero

> >is a flight as well in a mathematical sense, like an empty set is a

> >set as well.
>
> This is only true if the users of 'Fly()' expect the empty set as one of the
> possible options. However, this is not very likely. Especially if Penguin
> is added as an afterthought.
>

By invoking a requirement for encapsulation, the "empty set" is one of
the
possible effects for ANY method.

Ell

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
Robert O'Dowd <nos...@nonexistant.com> wrote:

#Robert C. Martin wrote:
#>
#> Daniel T. wrote in message ...
#> >
#> >The origional problem...
#> >> All birds fly, all penguins are birds, penguins don't fly.
#> >
#> >The key here is that the first premise is wrong.
#>
#> Granted.
#>
#> >It is incorrect to model
#> >falty premises in your code therefore "fly" *cannot* be included in
#> >"bird". This means that Elliott's answer is off.
#
#[Comments about mixins snipped].
#
#>
#> I agree that this is the proper way to think about the problem. However, I
#> disagree that all problems can be solved this way. The Bird/Penguin
#> proposition is an analogy to things that really do occurr in software. i.e.
#> it represent the notion that there are hierarchies in which most derivatives
#> have function 'f', but a minority of derivatives don't have any good
#> implementation for 'f'. Your solution assumes that dilemma can always be
#> resolved by the use of abstract thinking. Unfortunately, it cannot. The


#> Bird/Penguin dilemma really exists.

#>

#> Consider, for example, class Modem:
#>
#> class Modem
#> {
#> public:

#> virtual void Dial(char*) = 0;
#> virtual void Hangup() = 0;
#> virtual void Send(char) = 0;
#> virtual char Recv() = 0;
#> };
#>
#> Presume that hundreds of applications use the class. And that there are
#> many different derivatives of it.
#>
#> Now presume that a new kind of modem appears (actually a very old kind of
#> modem). A Cable modem. One that does not need to be dialled or hung up.
#>
#> We'd like all the programs that currently use Modem to be able to use
#> CableModem, which means that CableModem must be a derivative of Modem. But
#> CableModem does not have a reasonable implementation for Dial or Hangup.

#You were on a roll, but unfortunately picked a bad example.
#
#A reasonable implementation for Dial() and Hangup() is to do nothing.
#That would meet your requirement, and not break any code that only knows
#about the Modem class.

The goodness of having dial() and hangup() is based on the commonality found
in most used modems. If they are good for most modem types and usage, it's no
biggie to have other types like cable modem do nothing when given such a
call.. Otherwise leave the 2 methods out of the base modem.

Elliott

#The question with Bird can fly, Penguin is a bird that can not really
#brings
#up a question of what we do when we've analysed a problem, created a
#model, implemented it and then an example comes along that breaks our
#model.
#
#Clearly this sort of occurrence is a sign that our model is broken.
#The ideal solution is to redesign and reimplement the whole thing,
#possibly
#reusing key bits of code. If that isn't allowed, other pragmatic
#solutions may be to
#
#a) Say, for the sake of our model, that a Penguin is not a bird.
# Say it is an animal, that just happens to behave like a bird that
# can't fly. Remember our model is a representation that helps
# us get a job done.
#
#b) Turn Penguin's Fly() method into a no-op. After all, even if you
# tell a penguin to fly (and it understands you), it probably
# won't fly.
#
#BTW: If I'm told to fly, I'll book a ticket on an aircraft and
#use it to fly. This means I can fly. It doesn't mean I'm a bird.
#If it's important for solving a problem, however, I'll allow
#people to model me as a bird. They'd better document the reasons
#for that decision though!
#
#-<Automagically included trailer>
#Robert O'Dowd Ph +61 (8) 8259 6546
#MOD/DSTO Fax +61 (8) 8259 5139
#P.O. Box 1500 Email:
#robert...@dsto.defence.gov.au
#Salisbury, South Australia, 5108
#
#Disclaimer: Opinions above are mine and may be worth what you paid for
#them

Ell

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
#Robert C. Martin wrote:

#> Nikolai Pretzell wrote in message <19990701...@np-1105.stardiv.de>...
#> >
#> >Wouldn't be the (in many cases) best practical solution to this, to
#> >implement Dial() and Hangup() in class CableModem nevertheless, but as
#> >just doing nothing?

#> No, there is a good chance that the applications that use Modem were written
#> to expect that no characters would flow from the modems as long as they
#> weren't dialled. The CableModem *will* send characters before Dial is
#> called, and may crash those programs.

First, a client only looking for characters after successful dialing would
probably just ignore pre-dial characters.

Secondly, a cable modem with a dial method could use the method to know that
only characters it sends after receiving a dial() call are being seen by the
client.

Elliott

Ell

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
"Robert C. Martin" <rma...@oma.com> wrote:

#No, the callers of Fly() expect something to happen. If nothing happens
#because they happen to call Fly() on a Penguin, they we be confused, and
#possibly crash. To defend themselves from these crashes, the programmers
#will change their code to check to see if the Birds are Penguins before
#calling Fly().

All we have to do is have the Penguin say it flew 0.0 meters, if the protocol
of the Bird hierarchy is to return distance flown after class members receive
the fly() call.

I find this better than having to dynamic_cast to find out if a server
supports a method. Especially given that a client must cast for each possible
server class type. Gosh! Whenever a new Bird server type is added to the
hierarchy all clients must be rigged to know about it and dynamic_cast for it.
That's "yougly"! --> ugly**10.

It's much better for a class such as Penguin to just do nothing, or send 0.0
distance, depending on what clients expect. No cast, no fuss, no muss.

Jim Cochrane

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
In article <377C4E39...@nonexistant.com>,

Robert O'Dowd <nos...@nonexistant.com> wrote:
>Robert C. Martin wrote:
>>
>> ...

>>
>> >I think this resembles the „real life situation“: if you have 5 kinds
>> >of birds, one of which is a Penguin and request each of them to fly,
>> >that is exactly what would happen: The other 4 would fly and the
>> >Penguin would do nothing. So you don't need an exception, also you
>> >don't need make sure, that Penguins can not be requested to fly ( say
>> >have no Fly()-method ). The same with modems.
>>
>> No, the callers of Fly() expect something to happen. If nothing happens
>> because they happen to call Fly() on a Penguin, they we be confused, and
>> possibly crash. To defend themselves from these crashes, the programmers
>> will change their code to check to see if the Birds are Penguins before
>> calling Fly().
>
>This is only necessary if you do not use encapsulation properly in the
>Bird class.
>
>A basic tenet of encapsulation is that invoking a method may have an
>effect,
>but there is no specific requirement that it do so. For example,
>if a bird is told to fly, it may elect not to. This means that users
>of the Bird class must allow for the possibility that the Fly()
>method must do nothing. Otherwise you are breaking encapsulation
>of the Bird class, and allowing an object to get into an invalid
>state.

I wonder why no one has asked the question: What is the postcondition of
fly? If the postcondition is true - all possible states satisfy the
postcondition, then it is acceptable to implement fly to do nothing. If
the postcondition is that the creature has moved to a different location,
then implementing fly to do nothing will break the contract (and if an
assertion checking mechanism is being used will cause an assertion
violation during execution of that code). In this case, an implementation
that has the penguin walk or swim to a new location satisfies the
postcondition, unless the postcondition also requires that the creature
must actually fly. If that's the case, then you cannot code it correctly
unless you change the specification of the Bird interface or, in this
application, allow penguins to fly (if this conforms to the requirements,
then this may be OK). Another possibility may be to leave the code
incorrect, but ensure that the fly method is never called on a penguin, as
was suggested previously.

>Robert O'Dowd Ph +61 (8) 8259 6546

>MOD/DSTO Fax +61 (8) 8259 5139

>P.O. Box 1500 Email:
>robert...@dsto.defence.gov.au
>Salisbury, South Australia, 5108
>

>Disclaimer: Opinions above are mine and may be worth what you paid for

>them


--
Jim Cochrane
j...@dimensional.com

Ken Foskey

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
Jim Cochrane wrote:
>
> I wonder why no one has asked the question: What is the postcondition
> of fly? If the postcondition is true - all possible states satisfy
> the postcondition, then it is acceptable to implement fly to do
> nothing. If the postcondition is that the creature has moved to a
> different location, then implementing fly to do nothing will break the
> contract (and if an assertion checking mechanism is being used will
> cause an assertion violation during execution of that code). In this
> case, an implementation that has the penguin walk or swim to a new
> location satisfies the postcondition, unless the postcondition also
> requires that the creature must actually fly. If that's the case,
> then you cannot code it correctly unless you change the specification
> of the Bird interface or, in this application, allow penguins to fly
> (if this conforms to the requirements, then this may be OK). Another
> possibility may be to leave the code incorrect, but ensure that the
> fly method is never called on a penguin, as was suggested previously.
>

My pidgeon got a broken wing on landing and now cannot fly. Would
this crash your application?

I suppose what you are asking is what is the real business
requirement. The documentation may be faulty and the coder only
considered flying but is it really a requirement of movement.

This is an excellent thread, it makes me feel less dumb regarding
OO.
Ken

Marco Dalla Gasperina

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
Robert O'Dowd <nos...@nonexistant.com> wrote:
> Robert C. Martin wrote:
>> No, the callers of Fly() expect something to happen. If nothing happens
>> because they happen to call Fly() on a Penguin, they we be confused, and
>> possibly crash. To defend themselves from these crashes, the programmers
>> will change their code to check to see if the Birds are Penguins before
>> calling Fly().

> This is only necessary if you do not use encapsulation properly in the
> Bird class.

> A basic tenet of encapsulation is that invoking a method may have an
> effect,
> but there is no specific requirement that it do so. For example,
> if a bird is told to fly, it may elect not to. This means that users
> of the Bird class must allow for the possibility that the Fly()
> method must do nothing. Otherwise you are breaking encapsulation
> of the Bird class, and allowing an object to get into an invalid
> state.

Huh? Enacapulation has nothing to do with satisfying a contract. If
the post condition of Fly() is that the bird flew, then not flying
is an error (Most probably an exception).

> Also, by encapsulation, the Bird is responsible for maintaining it's own
> state. This means, before Fly() is invoked, that any bird must be
> in some valid state (be it hopping, pecking, or whatever).
> If flying would put a bird into an invalid state, (which would be
> the case for a flying penguin) it must refuse to fly.
> So, the Fly() method is responsible for ensuring that the bird
> is still in a realisable state.

> If an application can be adversely affected by the Bird class
> refusing to do something then (frankly) that is a problem
> for that application to resolve. The only contract that
> the Bird class can offer is to be in one of a defined
> set of states.

Nope. If the post-condition for Fly() is flight, then that is
a problem for the Penguin class to resolve.

> If a Bird enters an invalid state, then every application will
> be affected. By definition, if

> 1) a bird is in a valid state (whatever that may be)
> 2) bird is told to Fly()
> 3) bird refuses to Fly()

> then that bird is still in a valid state.

It may be in a valid state, but that has nothing to do with post
conditions, only invariants.

>> This is only true if the users of 'Fly()' expect the empty set as one of the
>> possible options. However, this is not very likely. Especially if Penguin
>> is added as an afterthought.
>>

> By invoking a requirement for encapsulation, the "empty set" is one of
> the possible effects for ANY method.

Only if you wish it so. I don't consider the possibility of failure
a good or robust design.

marco


Jim Cochrane

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
In article <377CB663...@zip.com.au>,

Ken Foskey <war...@zip.com.au> wrote:
>Jim Cochrane wrote:
>>
>> I wonder why no one has asked the question: What is the postcondition
>> of fly? If the postcondition is true - all possible states satisfy
>> the postcondition, then it is acceptable to implement fly to do
>> nothing. If the postcondition is that the creature has moved to a
>> different location, then implementing fly to do nothing will break the
>> contract (and if an assertion checking mechanism is being used will
>> cause an assertion violation during execution of that code). In this
>> case, an implementation that has the penguin walk or swim to a new
>> location satisfies the postcondition, unless the postcondition also
>> requires that the creature must actually fly. If that's the case,
>> then you cannot code it correctly unless you change the specification
>> of the Bird interface or, in this application, allow penguins to fly
>> (if this conforms to the requirements, then this may be OK). Another
>> possibility may be to leave the code incorrect, but ensure that the
>> fly method is never called on a penguin, as was suggested previously.
>>
>
>My pidgeon got a broken wing on landing and now cannot fly. Would
>this crash your application?

This would violate the postcondition - the code for the pidgeon would be
incorrect. (It would not crash the application because it would be caught
during inspection as a defect. :-) ) The solution is to either change
the postcondition to allow this or not to allow for birds that cannot fly
(whatever the reason).

>
>I suppose what you are asking is what is the real business
>requirement. The documentation may be faulty and the coder only
>considered flying but is it really a requirement of movement.

Exactly. One of the hardest tasks is getting the specifications right.

>
>This is an excellent thread, it makes me feel less dumb regarding
>OO.
>Ken


--
Jim Cochrane
j...@dimensional.com

Marco Dalla Gasperina

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
Marco Dalla Gasperina <mar...@pacifier.com> wrote:
> Only if you wish it so. I don't consider the possibility of failure
> a good or robust design.

Doh. I meant to write: I don't consider the possibility of failure
of every function a good or robust design.
^^^^^^^^^^^^^^^^^

marco


Boris Schaefer

unread,
Jul 2, 1999, 3:00:00 AM7/2/99
to
univ...@radix.net (Ell) writes:

| "Robert C. Martin" <rma...@oma.com> wrote:
|
| #No, the callers of Fly() expect something to happen. If nothing happens
| #because they happen to call Fly() on a Penguin, they we be confused, and
| #possibly crash. To defend themselves from these crashes, the programmers
| #will change their code to check to see if the Birds are Penguins before
| #calling Fly().
|
| All we have to do is have the Penguin say it flew 0.0 meters, if the protocol
| of the Bird hierarchy is to return distance flown after class members receive
| the fly() call.

After fly() is called on an bird is_flying() should be true, this is
part of the contract of fly() (I'd consider this reasonable). When
you call fly() on a penguin is_flying() will not return true after the
call and thus the contract of fly() is broken.

There is also the problem that flying birds can reach locations that
non-flying birds can reach. *Assuming* that EVERY location in the
world can be reached through flying (this is not reasonable in some
models, in other models it is), one can assume that an object that can
fly() can reach all locations in the world. Penguins while having a
method called fly() can not reach all locations in such a world. This
will make the caller of fly explicitly check for penguins and other
non-flying birds that contain a fly() method that does not make the
callee fly.

You could of course make a subclass of bird called non_flying and not
explicitely check for penguins but for non_flying and thus the
maintenance nightmare becomes less, but I still consider a flyer mixin
the better solution, because I think positive checking is better than
negative checking. In Eiffel, on the other hand, the problem is
solvable without a mixin by undefining fly() in penguin, but
unfortunately I think this cannot be done without relying on
closed-world assumptions like Eiffel does (AFAIK).

Essentially this is the same as the square->rectangle problem. Where
square inherits set_height() and set_width(), from rectangle although
this is unreasonable, because you cannot set the height of a square
independently of its width and you break the contract of square that
it has the same width and size. The difference between penguin->bird
and square->rectangle is that penguins breaks the contract of its
parent, while square breaks its own contract.

| I find this better than having to dynamic_cast to find out if a
| server supports a method. Especially given that a client must cast
| for each possible server class type. Gosh! Whenever a new Bird
| server type is added to the hierarchy all clients must be rigged to
| know about it and dynamic_cast for it. That's "yougly"! -->
| ugly**10.

No, they need to check dynamic_cast for Flyer, not for the specific
type of Flyer. Checking for Flyer is reasonable, considering that you
want to call fly().

| It's much better for a class such as Penguin to just do nothing, or
| send 0.0 distance, depending on what clients expect. No cast, no
| fuss, no muss.

I would consider a bird to be flying after calling fly(). Penguins
will not be flying after calling fly().

--
Boris Schaefer <s...@psy.med.uni-muenchen.de>

This fortune would be seven words long if it were six words shorter.

Ell

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to
Marco Dalla Gasperina <mar...@pacifier.com> wrote:

#Robert O'Dowd <nos...@nonexistant.com> wrote:
#> By invoking a requirement for encapsulation, the "empty set" is one of
#> the possible effects for ANY method.

#Only if you wish it so. I don't consider the possibility of failure
#a good or robust design.

But 0.0 flight distance should be acceptable to a client application.

Maxwell Sayles

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to

Ell wrote:

> "Robert C. Martin" <rma...@oma.com> wrote:
>
> #No, the callers of Fly() expect something to happen. If nothing happens
> #because they happen to call Fly() on a Penguin, they we be confused, and
> #possibly crash. To defend themselves from these crashes, the programmers
> #will change their code to check to see if the Birds are Penguins before
> #calling Fly().
>
> All we have to do is have the Penguin say it flew 0.0 meters, if the protocol
> of the Bird hierarchy is to return distance flown after class members receive
> the fly() call.

I think this depends on the post-condition. Typically the goal of flying the bird
would be to get him/her somewhere (as should be stated in the post-condition). So
I would consider it completely reasonable to have a penguin waddle there instead
of fly. As long as the post-condition and invariants are held who cares if the
feature is named incorrectly? :) Simply document what visible changes the feature
makes that way classes that redefine the feature can maintain the functionality
without breaking other code.

Maxwell Sayles


Michael Schuerig

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to
Boris Schaefer <s...@psy.med.uni-muenchen.de> wrote:

> After fly() is called on an bird is_flying() should be true, this is
> part of the contract of fly() (I'd consider this reasonable).

But only under the closed-world assumption. In general, you can't be
sure that your program is the only actor changing the world's state. So,
even if the postcondition of fly() is true, you can rely on it to stay
true.

Michael

--
Michael Schuerig
mailto:schu...@acm.org
http://www.schuerig.de/michael/

C. Keith Ray

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to
The Flyer Mixin (possibly implemented as an Interface in Java) also allows
adding flying mammels (such as Bats, or Flying Squirrels if you want to be
lenient) to the design. Or Flying Reptiles, such Pteranodons. Or Flying
Monkeys, if you want to venture into Oz.

C. Keith Ray

----------------

In article <ga3e3.1827$el4....@ord-read.news.verio.net>, "Robert C.
Martin" <rma...@oma.com> wrote:

> >rjbo...@aol.com.not (Richard J. Botting) wrote:
> >
> >#A couple of silly challenges for the OO community...
> >#how does your favorite
> >#OO technique handle the following two situations:
> >

> >#(B): All birds fly, all penguins are birds, penguins don't fly.
> >
> >1) Have the method that implements fly() in Penguin, deny it flight. 2)
> Have
> >the controller or manager that flies a bird know that Penguins can't.
>
> Unfortunately this means that everyone who uses the 'bird' class must also
> know about penguins, and every other exception. This is pretty ugly. (It
> violates the LSP)
>
> There are several approaches that are better. One is simply to keep 'fly'
> out of the Bird class, and to create a Flyable mixin that flying birds
> implement and that penguins do not:
>
> class Bird:
> {
> // no fly function
> };
>
> class Flyer
> {
> public:
> virtual void Fly() = 0;
> };
>
> class Sparrow : public Bird, public Flyer
> {
> };
>
> class Penguin : public Bird
> {
> };
>
> Now if you have a bird and you want to call the fly method:
>
> Bird *b = // some function that gives you a bird.
> if (Flyer *f = dynamic_cast<Flyer*>(b))
> {
> f->Fly();
> }
>
> Other approaches are to use Visitor, Acyclic Visitor, or Extension Object
> design patterns. All of which are basically ways of simulating
> dynamic_cast.

Kevin Kelley

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to
univ...@radix.net (Ell) wrote:

> Marco Dalla Gasperina <mar...@pacifier.com> wrote:
>
> #Robert O'Dowd <nos...@nonexistant.com> wrote:
> #> By invoking a requirement for encapsulation, the "empty set" is one of
> #> the possible effects for ANY method.
>
> #Only if you wish it so. I don't consider the possibility of failure
> #a good or robust design.
>
> But 0.0 flight distance should be acceptable to a client application.

Now you have code that compiles fine, and runs without error, but
does nothing... your birds are starving because they can't get to
their food. You've bypassed the compiler's error-checking and
replaced it with something that silently fails to fulfill expectations.

I think, if the act of flying (becoming airborne) is important then
fly() should be expressed as a mixin or interface (implements Flyer);
on the other hand if fly() is just a means of changing location then
it should be changed to a more abstract move(direction or destination)
method which is applicable to all types which inherit it.

The do-nothing implementation can have several problems; another one
is that when you later write code to handle penguins, you may not
bother to call it; then generalizing your code for all birds you
find it breaks.


Kevin Kelley

Boris Schaefer

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to
brou...@yahoo.com writes:

| Boris Schaefer <s...@psy.med.uni-muenchen.de> wrote:
|
| >In Eiffel, on the other hand, the problem is
| >solvable without a mixin by undefining fly() in penguin, but
| >unfortunately I think this cannot be done without relying on
| >closed-world assumptions like Eiffel does (AFAIK).
|

| Could you elaborate? I'm not very familiar with Eiffel.
|
| If you undefine fly() in penguin, what happens when a client calls fly() on
| a penguin object?

Neither am I very familiar with Eiffel, but from what I know it does a
whole lot of compile time global analysis to ensure that you cannot
call fly() on a penguin through a polymorphic call on a bird. You
would get a compile time type error when trying to do this.

Generally speaking this works by checking at compile time whether all
the types that an entity may have at run time support a method and
signaling an error, if you call method() on an object that may be of a
type that does not support method.

Unfortunately global analysis breaks, when you have the ability to
dynamically load classes at run time (at least I have never heard of
any solution to this).

--
Boris Schaefer <s...@psy.med.uni-muenchen.de>

Psychology. Mind over matter. Mind under matter? It doesn't matter.
Never mind.

Boris Schaefer

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to
schu...@acm.org (Michael Schuerig) writes:

| Boris Schaefer <s...@psy.med.uni-muenchen.de> wrote:
|
| > After fly() is called on an bird is_flying() should be true, this is
| > part of the contract of fly() (I'd consider this reasonable).
|
| But only under the closed-world assumption. In general, you can't be
| sure that your program is the only actor changing the world's state. So,
| even if the postcondition of fly() is true, you can rely on it to stay

^^^
| true.

I assume you meant "can't".

Well yes, you're right of course, but we were not really talking about
concurrency. If this where to be a concurrent world, then of course
you cannot rely on birds be flying after calling fly(), unless you're
in a critical section.

--
Boris Schaefer <s...@psy.med.uni-muenchen.de>

I couldn't remember when I had been so disappointed. Except perhaps the
time I found out that M&Ms really DO melt in your hand.
-- Peter Oakley

Boris Schaefer

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to
Boris Schaefer <s...@psy.med.uni-muenchen.de> writes:

| Well yes, you're right of course, but we were not really talking about
| concurrency. If this where to be a concurrent world, then of course
| you cannot rely on birds be flying after calling fly(), unless you're
| in a critical section.

... or have a lock on the bird.

--
Boris Schaefer <s...@psy.med.uni-muenchen.de>

Apathy Club meeting this Friday. If you want to come, you're not invited.

Factory

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to
In article <19990625165705...@ng-ci1.aol.com>,
rjbo...@aol.com.not says...

> A couple of silly challenges for the OO community...
> how does your favorite

> OO technique handle the following two situations:
>
> (B): All birds fly, all penguins are birds, penguins don't fly.
Hmm as some other ppl have pointed out this is a logical self-
contradiction, only two of the above statements can hold at a time, not
all three.

> (E): All elephants like their keepers, unless the keeper is mean in which case
> they don't like him, unless the elephant is Clide who likes all the keepers.

The basic problem in the above two situations point out is that one
will have a general rule which holds for a large amount of the class,
except for a few odd occasions.
This should be attacked in two ways when modelling:
1) think really, really hard about if a function can complete properly,
if it cannot, and if the calling code needs to know about it (and the
calling code prolly does) you need to inform it about the new situation.
2) the LSP afaik, says that the subclasses must be behave in a
predictable manner, as per the super class ( ie. to be substitutable ),
not that the methods of an object all complete successfully, or that all
the methods in the subclass implement it in a particular way.
Thus
class A
// do something, if unable to do something a DoingException will
// be thrown
abstract DoSomething()
...

class B
subclass of A

DoSomething()
{
throw DoingException
}

is ok, since B behaves as expected, but

class C
subclass of A

DoSomething()
{
// don't do anything
}

is bad since the calling code will have no idea of if something was
done, or not, it will most likely assume that something did happen.

Furthermore by doing:

class A
...

class B
subclass of A

DoSomething()

Limits the ability of DoSomething() to being an ability that a class
can either do, or not do.

This problem is already familiar to most (if not all) programmers, take
a look at opening a file in any library.

Of course throwing exceptions is A Bad Thing if you do it very often,
but if performance is a problem one can always degrade back to using
return values to indicate success.

But for the other side of the problem, that of being an existing
problem of a badly designed class structure:
class A
// do something
DoSomething()
...

Which demands that something must be done, and you have to add in a
subclass of A which cannot possibly do something, either always or
sometimes. You then have a couple of choices:
- Apply the idea that nothing is something. For the example of
the cablemodem, the Dial() command (I assume) changes the device from a
state which it cannot send/receive data to a state where it can. This
can be modelled easily, since all it requires is removing abilities at
certain times. This can also adhere to the LSP.
But this solution doesn't work when the function being called is a
demand for a change of state that the object cannot either get to, or
affect.

- Fix the erroneous class structure, the benefits and drawbacks of this
should be pretty obvious.

- Develop a new class, class A', which doesn't hold a function called
DoSomething(), and which is not a subclass of A, and derive your new
class B from that, this has the problem of code duplication, and that
B now has to be handled differently to subclasses of A.

Blah, end of rant..

- Factory, who's anti-virus software is still chugging through 4 gig of
.zip files..

Ell

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to
kel...@ruralnet.net (Kevin Kelley) wrote:

#univ...@radix.net (Ell) wrote:
#


#> Marco Dalla Gasperina <mar...@pacifier.com> wrote:
#>

#> #Robert O'Dowd <nos...@nonexistant.com> wrote:
#> #> By invoking a requirement for encapsulation, the "empty set" is one of
#> #> the possible effects for ANY method.
#>
#> #Only if you wish it so. I don't consider the possibility of failure
#> #a good or robust design.
#>
#> But 0.0 flight distance should be acceptable to a client application.
#
#Now you have code that compiles fine, and runs without error, but
#does nothing... your birds are starving because they can't get to
#their food. You've bypassed the compiler's error-checking and
#replaced it with something that silently fails to fulfill expectations.
#
#I think, if the act of flying (becoming airborne) is important then
#fly() should be expressed as a mixin or interface (implements Flyer);
#on the other hand if fly() is just a means of changing location then
#it should be changed to a more abstract move(direction or destination)
#method which is applicable to all types which inherit it.
#
#The do-nothing implementation can have several problems; another one
#is that when you later write code to handle penguins, you may not
#bother to call it; then generalizing your code for all birds you
#find it breaks.

But you should probably be checking that a Bird actually flew for every
species of Bird. Also we are making assumptions about what fly() involves.
What is the actual contract about fly()? But whatever it is, you probably
always want to check the success of a flight, or how far a flight went. A
client doing such checking should be able to accommodate a Bird that only goes
0.0 km, when told to fly.

Robert Oliver

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to
Ell wrote:
>
> But you should probably be checking that a Bird actually flew for every
> species of Bird. Also we are making assumptions about what fly() involves.
> What is the actual contract about fly()? But whatever it is, you probably
> always want to check the success of a flight, or how far a flight went. A
> client doing such checking should be able to accommodate a Bird that only goes
> 0.0 km, when told to fly.

This is nonsense! There is nothing inherent in the Bird class or fly
method that
suggests fly might fail by design. A programmer has the right to expect
a class to do
what it is asked. That's what a contract is, a guarantee that the class
will
perform when certain conditions are met. A programmer may need to check
conditions,
but should never have to check success to compensate for poor design.
If a
descendant breaks the contract of a base class it is a bad design. No
amount
of checking after the call can make it a good design again. Does one
need to check
every method called to see if it succeeded because someone might break
the
contract later on? No one should have to do this. No one does.

This situation arose because a new class, penguin, was developed that
had not been considered during the initial design. In a real design I
would guess
that the designer looked at the world as it existed in his problem
domain and
built the system too closely modeling that domain. Later when the
domain changed,
the system was found to be too brittle. If a designer considered that
the fly
method might not work in the future, the correct response is not to
check for
it's success each time, but to use one of the other techniques or
abstractions
mentioned elsewhere in this thread.

On the other hand, if you really do code like this, I suggest you rename
your
methods by prefixing them with "TryTo..." and adding a "DidIt..." method
for
those that don't already return success. Then you could call TryToFly
and
DidItFly which more accurately reflect the semantics of what you
suggest.

Of course, when you implement DidItFly you might implement it by taking
the
difference between its current and previous position. If so, it won't
work
anyhow. You might not even have visibility into how DidIt... works. It
could
be part of a library for which you haven't the source.

Regards

Bob Oliver

Phlip

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to
Ell wrote:

>But you should probably be checking that a Bird actually flew for
every
>species of Bird. Also we are making assumptions about what fly()
involves.
>What is the actual contract about fly()? But whatever it is, you
probably
>always want to check the success of a flight, or how far a flight
went. A
>client doing such checking should be able to accommodate a Bird
that only goes
>0.0 km, when told to fly.

This thread is about how code changes over a long period of time.

Here's the primary problem:

In the beginning, some hack wrote a Bird class with a method Fly.
This did not return a distance flown, or a boolean indicating
success.

This very bad code was very successful, and everyone used it
everywhere. Maybe the company sold it as a library.

Then someone requested Penguins, Kiwis, Moas, Dodos and Emus in the
class library, and the vendor added them by giving them empty Fly
methods.

Now all the client code breaks when their Bird pointers point to
objects that are really flightless. The code breaks because it was
built using assumptions that were part of the original contract, but
which were un-testable.

This thread is about cleaning up after an interface with very bad
DbC becomes widely used, but then needs a OCP upgrade. You can't say
"but the client code should have enforced the contract" because,
with the first version of the code, the Bird interface had a
complete contract but no system to enforce it besides guessing where
the bird went after Flight.

If this thread is about the first version providing bad DbC and the
first clients not enforcing it, then you can't say "the first
version should have provided good DbC and the first clients should
have enforced it."

This thread is about cleaning up the mess afterwards. Sometimes we
get paid to do that. The pay is good, and our results might be
stable. But, again, hindsight is 20/20.

Harry Chomsky

unread,
Jul 3, 1999, 3:00:00 AM7/3/99
to
Boris Schaefer wrote in message ...

>brou...@yahoo.com writes:
>| If you undefine fly() in penguin, what happens when a client calls fly()
on
>| a penguin object?
>
>Neither am I very familiar with Eiffel, but from what I know it does a
>whole lot of compile time global analysis to ensure that you cannot
>call fly() on a penguin through a polymorphic call on a bird. You
>would get a compile time type error when trying to do this.
>
>Generally speaking this works by checking at compile time whether all
>the types that an entity may have at run time support a method and
>signaling an error, if you call method() on an object that may be of a
>type that does not support method.
>
>Unfortunately global analysis breaks, when you have the ability to
>dynamically load classes at run time (at least I have never heard of
>any solution to this).

I think the situation is actually quite a bit worse than this. Even with
global analysis in a completely closed world, the compiler can't always
predict exactly what behavior your program _might_ have when it's run. The
compiler can try to prove things about the program -- it might be able to
prove that a certain call to fly() will happen on a penguin object, or it
might be able to prove that a certain call to fly() will _never_ happen on a
penguin object. But in general, there can be calls to fly() the the
compiler isn't sure about. So a globally analyzing compiler has two
choices:

- If unsure whether a certain call is safe, allow it

- If unsure whether a certain call is safe, prohibit it

My understanding is that current Eiffel compilers take the first approach,
and it has been proposed that future compilers might take the second
approach. In other words, current compilers allow you to write code that
will generate run-time type errors, and future compilers might reject this
code. Or to look at it a different way, current compilers trust the
programmer to take care of type safety, while future compilers might reject
code that "looks" unsafe even if the programmer knows it's actually safe.

Of course, if you open up the world by dynamically loading classes, then a
call that was previously provably safe may suddenly become unsafe, so even a
restrictive compiler won't be able to prevent run-time type errors.

Disclaimer: I'm not an Eiffel expert. Eiffel experts, please correct me if
I'm wrong!

Ell

unread,
Jul 4, 1999, 3:00:00 AM7/4/99
to
Robert Oliver <oli...@erols.com> wrote:

#Ell wrote:
#>
#> But you should probably be checking that a Bird actually flew for every
#> species of Bird. Also we are making assumptions about what fly() involves.
#> What is the actual contract about fly()? But whatever it is, you probably
#> always want to check the success of a flight, or how far a flight went. A
#> client doing such checking should be able to accommodate a Bird that only goes
#> 0.0 km, when told to fly.
#
#This is nonsense! There is nothing inherent in the Bird class or fly
#method that
#suggests fly might fail by design. A programmer has the right to expect
#a class to do
#what it is asked. That's what a contract is, a guarantee that the class
#will
#perform when certain conditions are met. A programmer may need to check
#conditions,
#but should never have to check success to compensate for poor design.
#If a
#descendant breaks the contract of a base class it is a bad design. No
#amount
#of checking after the call can make it a good design again. Does one
#need to check
#every method called to see if it succeeded because someone might break
#the
#contract later on? No one should have to do this. No one does.
#
#This situation arose because a new class, penguin, was developed that
#had not been considered during the initial design. In a real design I
#would guess
#that the designer looked at the world as it existed in his problem
#domain and
#built the system too closely modeling that domain. Later when the
#domain changed,
#the system was found to be too brittle. If a designer considered that
#the fly
#method might not work in the future, the correct response is not to
#check for
#it's success each time, but to use one of the other techniques or
#abstractions
#mentioned elsewhere in this thread.
#
#On the other hand, if you really do code like this, I suggest you rename
#your
#methods by prefixing them with "TryTo..." and adding a "DidIt..." method
#for
#those that don't already return success. Then you could call TryToFly
#and
#DidItFly which more accurately reflect the semantics of what you
#suggest.

In the course of 20 years programming, I see where typically a call such as
fly() would return distance flown, and typically the client checks the return
for distance flown. Only a poorly designed client in this kind of situation
would just act is if it knew a Bird flew the distance requested. Simple as
that.

Elliott


#Of course, when you implement DidItFly you might implement it by taking
#the
#difference between its current and previous position. If so, it won't
#work
#anyhow. You might not even have visibility into how DidIt... works. It
#could
#be part of a library for which you haven't the source.
#
#Regards
#
#Bob Oliver

Ell

unread,
Jul 4, 1999, 3:00:00 AM7/4/99
to
Robert Oliver <oli...@erols.com> wrote:

#This situation arose because a new class, penguin, was developed that
#had not been considered during the initial design. In a real design I

#would guess that the designer looked at the world as it existed in his problem
#domain and built the system too closely modeling that domain. Later when the
#domain changed, the system was found to be too brittle.

Oh, how wonderful it must be to bring a prejudice to the discussion and try to
make that the issue. In fact the initial situation stated by Botting was
this:

>Richard J. Botting wrote in message
># A couple of silly challenges for the OO community...
># how does your favorite
># OO technique handle the following two situations:
>#
>#(B): All birds fly, all penguins are birds, penguins don't fly.

So it wasn't your fantasy of someone creating an implementation design that
was too close to the domain and then the design being brittle as a result of
requested changes,

And only an incompetent programmer is generally incapable of creating a
software implementation system that reflects the nature of domain objects and
relationships as they are relevant to project use cases, which is brittle
given the demands of the typically expected kinds of changes that are required
of the software system.

Elliott

Boris Schaefer

unread,
Jul 4, 1999, 3:00:00 AM7/4/99
to
"Harry Chomsky" <har...@chomsky.net> writes:

| >Unfortunately global analysis breaks, when you have the ability to
| >dynamically load classes at run time (at least I have never heard of
| >any solution to this).
|
| I think the situation is actually quite a bit worse than this. Even
| with global analysis in a completely closed world, the compiler
| can't always predict exactly what behavior your program _might_ have
| when it's run. The compiler can try to prove things about the
| program -- it might be able to prove that a certain call to fly()
| will happen on a penguin object, or it might be able to prove that a
| certain call to fly() will _never_ happen on a penguin object. But
| in general, there can be calls to fly() the the compiler isn't sure
| about.

Well, you're right of course, the compiler cannot predict program
behaviour. Simple case would be:

bird* b;
char key = read_keyboard();
if(key == 'f')
b = new falcon();
else if(key == 'p')
b = new penguin();
b.fly();

The problem is of course of general nature, when you have external
input, the compiler cannot predict program behaviour.

| So a globally analyzing compiler has two choices:
|
| - If unsure whether a certain call is safe, allow it
|
| - If unsure whether a certain call is safe, prohibit it
|
| My understanding is that current Eiffel compilers take the first approach,
| and it has been proposed that future compilers might take the second
| approach.

From my understanding of OOSC2, I get the impression that Eiffel
prohibits unsafe calls. I don't know, what the compilers implement,
but at least ISE's compiler probably rejects such calls, given Meyer's
emphasis on correctness in OOSC2.

The biggest problem of global analysis is probably that you need an
amazing amount of control flow analysis to not be overly restrictive
in what calls you allow. The more restrictive you get, the easier it
is to implement.

<snip>

| Of course, if you open up the world by dynamically loading classes, then a
| call that was previously provably safe may suddenly become unsafe, so even a
| restrictive compiler won't be able to prevent run-time type errors.

Well, you could disallow loading of classes that undefine methods.
This is restrictive, but at least better than having no class loading
at all.

--
Boris Schaefer <s...@psy.med.uni-muenchen.de>

There are ten or twenty basic truths, and life is the process of
discovering them over and over and over.
-- David Nichols

Phlip

unread,
Jul 4, 1999, 3:00:00 AM7/4/99
to
Ell wrote:

>>Richard J. Botting wrote in message

>>#(B): All birds fly, all penguins are birds, penguins don't fly.
>
>So it wasn't your fantasy of someone creating an implementation
design that
>was too close to the domain and then the design being brittle as a
result of
>requested changes,

Richard's sentence is internally self-contradictory.

The very first clause represents a mismatch between real-world
objects and their software counterparts. _Real_ software is
frequently much more confusing than this simple example, so this
kind of mistake can easily happen to those of us who are not
unquestionably infallibly perfect.

Richard's self-contradiction was short-hand to indicate we were
expected to discuss the situation where the mistake had already been
made.

The question makes no sense to ask if we don't expect that the
mistake happened and then needed to be dealt-with as cleanly as
possible in later code. Otherwise, the answer is simple: Create a
base class called Bird, and two immediate child classes, each with a
slightly larger interface. The one for FlightlessBird does not
contain a Fly function. End of crisis. (Yes, I know there are other
ways to solve it, but they are simple too, and do not deserve a
thread with ~70 posts.)

>And only an incompetent programmer is generally incapable of
creating a
>software implementation system that reflects the nature of domain
objects and
>relationships as they are relevant to project use cases, which is
brittle
>given the demands of the typically expected kinds of changes that
are required
>of the software system.

Ayup. If "incompetent" defines as "made a mistake in a protocol
class", then that's the kind of programmer we are discussing.

And only a competent one can clean up, and can discuss the ways to
clean up without solutions that involve time travel.

--
Phlip at politizen dot com (address munged)
======= http://users.deltanet.com/~tegan/home.html =======

"All men are mortal, Socrates was a man, all men are Socrates."
-- Woody Allen

Maxwell Sayles

unread,
Jul 4, 1999, 3:00:00 AM7/4/99
to

brou...@yahoo.com wrote:

> Boris Schaefer <s...@psy.med.uni-muenchen.de> wrote:
>
> >In Eiffel, on the other hand, the problem is
> >solvable without a mixin by undefining fly() in penguin, but
> >unfortunately I think this cannot be done without relying on
> >closed-world assumptions like Eiffel does (AFAIK).
>
> Could you elaborate? I'm not very familiar with Eiffel.
>

> If you undefine fly() in penguin, what happens when a client calls fly() on
> a penguin object?

My understanding of Eiffel is that if you undefine a feature you can not
instantiate that class (it becomes a deferred or abstract class). But I
haven't tested this yet so it may be untrue.

Maxwell Sayles


nsi5700000-Perryman

unread,
Jul 5, 1999, 3:00:00 AM7/5/99
to
Ken Foskey <war...@zip.com.au> writes:

> My pidgeon got a broken wing on landing and now cannot fly. Would
> this crash your application?

> I suppose what you are asking is what is the real business


> requirement. The documentation may be faulty and the coder only
> considered flying but is it really a requirement of movement.

Totally correct.

What you are seeing here is the OOD/OOP viewpoint of how to deal with
penguins. However, all solutions, good as they may be, are working under
fundamental (and as we've seen here, different) assumptions.

IMHO this thread as proved the critical importance of nailing the
requirements, and doing a subsequent correct analysis. Design or coding
by itself just led you all into lengthy argument ... :-)


Regards,
Steven Perryman

Robert C. Martin

unread,
Jul 5, 1999, 3:00:00 AM7/5/99
to

Robert O'Dowd wrote in message <377C4E39...@nonexistant.com>...
>Robert C. Martin wrote:
>>
>> Nikolai Pretzell wrote in message
<19990701...@np-1105.stardiv.de>...
>>
>> >Wouldn't be the (in many cases) best practical solution to this, to
>> >implement Dial() and Hangup() in class CableModem nevertheless, but as
>> >just doing nothing?
>>
>> No, there is a good chance that the applications that use Modem were
written
>> to expect that no characters would flow from the modems as long as they
>> weren't dialled. The CableModem *will* send characters before Dial is
>> called, and may crash those programs.
>
>My understanding is that Dial and Hangup of modems is achieved by
>squirting
>control characters at the modem.

Some modems work that way, others don't. There are LOTS of different kinds
of modems. Not all follow the Hayes convention.

Robert C. Martin

unread,
Jul 5, 1999, 3:00:00 AM7/5/99
to

Robert O'Dowd wrote in message <377C4E39...@nonexistant.com>...
>Robert C. Martin wrote:

>>
>> No, the callers of Fly() expect something to happen. If nothing happens

>> because they happen to call Fly() on a Penguin, they we be confused, and

>> possibly crash. To defend themselves from these crashes, the programmers

>> will change their code to check to see if the Birds are Penguins before

>> calling Fly().
>
>This is only necessary if you do not use encapsulation properly in the
>Bird class.
>
>A basic tenet of encapsulation is that invoking a method may have an
>effect,
>but there is no specific requirement that it do so. For example,
>if a bird is told to fly, it may elect not to.

I don't know who told you that, but it's quite a bizzare concept. Can you
imagine a telling a stack to push a data item, and then having it elect not
to? The contract between the caller and the function *may* allow for the
function to be a no-op, but certainly most contracts expect the function to
do something and cannot tolerate no-op behavior.

Encapsulation is the shielding of callers from the data/implementation
within the server object. Encapsulation does not mean that all callers must
assume that functions may be implemented as no-ops.

>
>If an application can be adversely affected by the Bird class
>refusing to do something then (frankly) that is a problem
>for that application to resolve.

If writer's of reusable frameworks took this attitude, their frameworks
would be decidedly unpopular. Irrespective of that, if we have dozens of
applications that assumed that Birds can Fly, and if those dozens of
applications are deployed at thousands of user facilities; we simply are not
going to modify them, test them, re-release them, and redeploy them just
because somebody decided that Birds can elect not to fly sometimes.

>
>By invoking a requirement for encapsulation, the "empty set" is one of

>the
>possible effects for ANY method.


file.close()
System.exit()
Semaphore.release()
XrayProjector.EmergencyShutoff();
NuclearMissile.Abort();
NuclearPowerPlant.EmergencyCoolingSystemActivation();

/* need I go on */

Encapsulation does not imply that the empty set is a legal outcome of any
method. It merely hides users from implementation details. It's hard to
classify the expected behavior of a function as an implementation detail.

Robert C. Martin

unread,
Jul 5, 1999, 3:00:00 AM7/5/99
to

Robert O'Dowd wrote in message <377C4E39...@nonexistant.com>...
>Robert C. Martin wrote:
>>
>> Nikolai Pretzell wrote in message
<19990701...@np-1105.stardiv.de>...
>>
>> >Wouldn't be the (in many cases) best practical solution to this, to
>> >implement Dial() and Hangup() in class CableModem nevertheless, but as
>> >just doing nothing?
>>
>> No, there is a good chance that the applications that use Modem were
written
>> to expect that no characters would flow from the modems as long as they
>> weren't dialled. The CableModem *will* send characters before Dial is
>> called, and may crash those programs.
>
>If applications assume that the modems won't send characters before
>Dial()
>is invoked, then the CableModem implementation needs to include some
>intelligence to mimic that behaviour. The simplest way I can think of
>is creation of a class-specific data cache that catches any data
>received
>from the modem before Dial() is called. The Dial() method, instead
>of being a no-op, would allow data from that cache to become available.

Now consider the plight of the poor application programmers that must use
CableModem directly. By emulating Dial/Hangup in the CableModem, you have
forced these application programers to call Dial! That's something they are
not going to be very pleased about. Moreover, you have created a dependency
between CableModem and everything associated with Dialling. So, for
example, when we run out of area codes in the next decade, and the dialling
protocol changes such that the Dial function of Modem needs to take
different arguments, all the application programs that use CableModem
directly will have to be changed, recompiled, retested, and redistributed,
because they have to call Dial! Ack!!

Robert C. Martin

unread,
Jul 5, 1999, 3:00:00 AM7/5/99
to

nsi5700000-Perryman wrote in message ...

>IMHO this thread as proved the critical importance of nailing the
>requirements, and doing a subsequent correct analysis. Design or coding
>by itself just led you all into lengthy argument ... :-)


That's fine. I agree that spending time on getting the requirements right
is a good thing to do. But it's also an activity that is doomed to fail.
The requirements are simply never right; and even if they are then, just
wait a minute.

It is not outlandish to expect that the initial requirements writers simply
never conceived of the notion of a flightless bird. They framed the
requirements in such a way as to set that expectation amongst all the
engineers as well. And so the Bird class was written with a postcondition
that demanded flight. And many application programs, having seen that
postcondition, wrote their code accordingly. Thus the introduction of
flighless birds breaks an huge structure of software.

How could we have prevented this? By employing the Interface Segregation
Principle. (ISP) (See "The Interface Segregation Principle" in the
'publications' section of http://www.oma.com.) If, at the very start, we
had segregated the interface of the Bird class into interfaces that were
based upon clients; then all the clients who call Fly would have been
written to accept a Flyer, and not a Bird. All Bird derivatives would have
derived from Bird, and from Flyer. Users that wished to call Fly, but were
given a Bird, would have obtained the Flyer interface either through
dynamic_cast (in C++), instanceOf (in Java) or Visitor (any language).

The ISP is one of many such design principles that help us create software
that is generally more susceptable to change.

Robert C. Martin

unread,
Jul 5, 1999, 3:00:00 AM7/5/99
to

Ell wrote in message <3780a596...@news1.radix.net>...

>In the course of 20 years programming, I see where typically a call such as
>fly() would return distance flown, and typically the client checks the
return
>for distance flown. Only a poorly designed client in this kind of
situation
>would just act is if it knew a Bird flew the distance requested. Simple as
>that.


So, you've never written: file.close(); without checking the return code?

Or you've never let an fstream object fall out of scope, implicitly closing
the file in the destructor?

There are many function that we simply assume will work as advertised. When
the requirements change and break those expectations, everyone dependent
upon those expectations, either directly or transitively, must be fixed. In
our designs, we want to limit the number of such transitive dependencies as
is feasible so that the impact of such fixes will be small.

Robert C. Martin

unread,
Jul 5, 1999, 3:00:00 AM7/5/99
to

Ell wrote in message <37837090...@news1.radix.net>...

>"Robert C. Martin" <rma...@oma.com> wrote:
>
>#No, the callers of Fly() expect something to happen. If nothing happens
>#because they happen to call Fly() on a Penguin, they we be confused, and
>#possibly crash. To defend themselves from these crashes, the programmers
>#will change their code to check to see if the Birds are Penguins before
>#calling Fly().
>
>All we have to do is have the Penguin say it flew 0.0 meters, if the
protocol
>of the Bird hierarchy is to return distance flown after class members
receive
>the fly() call.

Must we really go into all the applications and find every place where they
called Fly and then check to see if the distance was zero? Can we afford
the workload of modifying all those applications and redeploying them?

Or would we rather disallow those programs from seeing Penguins by refusing
to derive Penguin from Bird.


>
>I find this better than having to dynamic_cast to find out if a server
>supports a method. Especially given that a client must cast for each
possible
>server class type. Gosh! Whenever a new Bird server type is added to the
>hierarchy all clients must be rigged to know about it and dynamic_cast for
it.
>That's "yougly"! --> ugly**10.

Well, perhaps you are reading too much into it. The client only has to do
the dynamic_cast if it knows for sure that the interface exists; and then
only if he wants to invoke the interface. New server types don't affect the
client unless he is going to invoke them. And if he is going to invoke
them, then he's going to have to change anyway to call the methods.

This is the fundemental principle behind COM. First ask for an interface,
then invoke it.


>
>It's much better for a class such as Penguin to just do nothing, or send
0.0
>distance, depending on what clients expect. No cast, no fuss, no muss.


Just crashes from those programs that expected Fly to do something.

Ell

unread,
Jul 6, 1999, 3:00:00 AM7/6/99
to
univ...@radix.net (Ell) wrote:

#So it wasn't your fantasy of someone creating an implementation design that
#was too close to the domain and then the design being brittle as a result of
#requested changes,
#
#And only an incompetent programmer is generally incapable of creating a
#software implementation system that reflects the nature of domain objects and
#relationships as they are relevant to project use cases, which is brittle
#given the demands of the typically expected kinds of changes that are required
#of the software system.

Correction:

#And only an incompetent programmer is generally incapable of creating a
#software implementation system that reflects the nature of domain objects and
#relationships as they are relevant to project use cases, which is
*not*
#brittle given the demands of the typically expected kinds of changes that are
#required of the software system.

Daniel T.

unread,
Jul 6, 1999, 3:00:00 AM7/6/99
to
"Robert C. Martin" <rma...@oma.com> wrote:

>>This would not violate the LSP, because you can request Fly() from a
>>Penguin like all other birds. He just would not move. A flight of zero
>>is a flight as well in a mathematical sense, like an empty set is a
>>set as well.
>
>This is only true if the users of 'Fly()' expect the empty set as one of the
>possible options. However, this is not very likely. Especially if Penguin
>is added as an afterthought.

I would like to delve into this further. Permit me to abstract it:

class Foo:
def methodA(self):

def methodB(self):

def methodC(self):

#etc...

Say you have the above class (writen in Python) which has been used
extensivly in our app/framework and we now find that we have another class
that works exactly the same (call it Bar) but doesn't implement methodA.
We would like to be able to use it in place of Foo. LSP says we simply
cannot do it, yes? And in fact, it is quite probable that LSP is true
(even if we simply have Bar implament an empty methodA.)

So what are we to do? Holisticly, we should go back to the drawing board
and re-design the classes yes?

Daniel T.

unread,
Jul 6, 1999, 3:00:00 AM7/6/99
to

>"Robert C. Martin" <rma...@oma.com> wrote:
>

>#No, the callers of Fly() expect something to happen. If nothing happens
>#because they happen to call Fly() on a Penguin, they we be confused, and
>#possibly crash. To defend themselves from these crashes, the programmers
>#will change their code to check to see if the Birds are Penguins before
>#calling Fly().
>
>All we have to do is have the Penguin say it flew 0.0 meters, if the protocol
>of the Bird hierarchy is to return distance flown after class members receive
>the fly() call.

Some birds fly 100m per trip, some fly 1000m per trip. The clinet wants
the bird to go from point A to point B (which happens to be 1500m away.)
So in the client, there is a loop like this:

while (bird.location() != B)
{
bird.fly(B);
}

All hell breaks loose if a Penguin is sent to the above loop...

Simon Harris

unread,
Jul 6, 1999, 3:00:00 AM7/6/99
to
I just started reading this thread so please forgive me if it's been stated
before or whatever. I see your point about expected behaviour but what would
consider an appropriate solution for the following very theoretical example?

class Plane {
Vector cargo;

addCargo();
Vector getCargo();
etc...

takeOff() {
if sum of the weight of all elements in cargo < 1000 {
we can fly
} else {
too heavy!!!
}
}
}


--
Simon Harris

sha...@connect.net.au
Haruki...@yahoo.com

Robert Oliver

unread,
Jul 6, 1999, 3:00:00 AM7/6/99
to
Ell wrote:
>
> In the course of 20 years programming, I see where typically a call such as
> fly() would return distance flown, and typically the client checks the return
> for distance flown. Only a poorly designed client in this kind of situation
> would just act is if it knew a Bird flew the distance requested. Simple as
> that.
>
Clearly it is irresponsible on the part of a programmer to ignore
checking a call that is known to fail sometimes. Some functions can not
be written such that they always succeed and therefore must be tested
(or an exception caught). What I was reacting to was the implication
that it is typical for a client not to trust a contract. When I am
using an abstract base class I don't check to see that subclasses adhere
to the contract ( except sometimes when debugging ). You seemed to
think checking wasn't necessary in another part of the thread whey you
said:

Ell wrote:
> I think it makes great sense. No checking necessary, Penguin just doesn't
> fly. But it is a valid member of the hierarchy because it so bird like in
> most other respects.
>
>Elliott

So how is a programmer to know when this kind of checking is needed?
And, if one thinks it may be needed, doesn't it make sense to specify
how it will be handled in the contract?

Regards,

Bob Oliver

Ell

unread,
Jul 6, 1999, 3:00:00 AM7/6/99
to
dani...@gorilla.com (Daniel T.) wrote:

#In article <37837090...@news1.radix.net>, univ...@radix.net wrote:
#


#>"Robert C. Martin" <rma...@oma.com> wrote:
#>

#>#No, the callers of Fly() expect something to happen. If nothing happens
#>#because they happen to call Fly() on a Penguin, they we be confused, and
#>#possibly crash. To defend themselves from these crashes, the programmers
#>#will change their code to check to see if the Birds are Penguins before
#>#calling Fly().
#>
#>All we have to do is have the Penguin say it flew 0.0 meters, if the protocol
#>of the Bird hierarchy is to return distance flown after class members receive
#>the fly() call.
#
#Some birds fly 100m per trip, some fly 1000m per trip. The clinet wants
#the bird to go from point A to point B (which happens to be 1500m away.)
#So in the client, there is a loop like this:
#
#while (bird.location() != B)
#{
# bird.fly(B);
#}
#
#All hell breaks loose if a Penguin is sent to the above loop...

But my point is that this client should be checking for actual movement by the
server class - Bird - and subclasses - Penguin, etc. It is a poorly designed
client indeed which does not check the return results from a request to a
server. I mean come on, that's Programming 101. You never make a request and
assume it was fulfilled. You almost always check before proceeding unless the
call is trivial.

if (washCar() == False)
return;
else
doMoreStuff();
end if

Ell

unread,
Jul 6, 1999, 3:00:00 AM7/6/99
to
Simon Harris <sha...@connect.net.au> wrote:

#I just started reading this thread so please forgive me if it's been stated
#before or whatever. I see your point about expected behaviour but what would
#consider an appropriate solution for the following very theoretical example?
#
#class Plane {
# Vector cargo;
#
# addCargo();
# Vector getCargo();
# etc...
#
# takeOff() {
# if sum of the weight of all elements in cargo < 1000 {
# we can fly
# } else {
# too heavy!!!
# }
# }
#}

Precisely my point. In such a situation a client in this case Plane should
always check before doing some critical operation.

Phlip

unread,
Jul 6, 1999, 3:00:00 AM7/6/99
to
Graham Perkins wrote:

> Phlip wrote:
> > Scenario A: You scream at the penguin "FLY FLY FLY!" The
terrified
> > animal makes a short hop away from you. This registers as flight
> > with a non-zero distance, and it satisfies your interface
contract.
>
> Sounds good!
> Or push it over edge of cliff and watch it fly with velocity
> vector <0,sqrt(2gs)>.

The contract was a success but the object died.

(Did all the clients remember to check if the bird was still alive
after the flight? - maybe if You-Know-Who wrote them all ;-)

> > Scenario B: Remember the episode of The Simpsons where Bart
> > discovered a comet was headed directly towards Moe's Tavern?...
>
> Vaguely, but I can't remember how it turned out. Please remind
> me.

The shadow of the asteroid is looming over the city. POV asteriod,
tracking its shadow on the ground as it nears its target. Various
Springfield establishments move thru the view into the shadow. As we
enter the city zoo, in the Antarctic display all the penguins look
up at the asteroid as it blots out the sun, then flap their wings
vigorously and fly away in a flock.

> Unfortunately my newsreader won't post at the moment.

I took the liberty of throwing this into the group because it was
not, like, intimate or anything...

> I wanted
> to join this debate to point out how inappropriate the natural
> world taxonomy is as an analogy to software artefacts. Before
> 1859, the Linnean taxonomy showed how animals appeared to be
> related on the basis of their anatomical structures, or
> "phenotypes". Since 1859, the taxonomy has been much hacked
> about with, and projected back in time, to show genectic
> evolution of each creature.
>
> The evolution of flight is a key factor in many characteristics
> of what we now call birds, but is not a necessary property, and
> has also been evolved by other groups. In the past there were
> many more species of non-flying birds, and they came close to
> dominating the planet. The crucial thing about birds is that
> they have a common ancestor and all share enough DNA to prevent
> us calling them something else.
>
> So our animal classification system is based on the subset
> relations between internal code sequences. These are almost
> entirely irrelevant in software, where we need classification
> systems based on external interface subsets.
>
> I find it infuriating to see so many animal analogies cropping
> up time and again! I wish people would make the effort to find
> better analogies from other more physical areas of human
> technology.

That's the thing; our tutorials need clear examples for some
subjects and real ones for others. I'm always for the real-code
examples, but then I attempt to read Booch's OO Applications book
(orange cover; full of real-code examples) and it's like trying to
eat all the way from one end of a Wheat Thin to the other. But...

IIF our Bird taxonomy is found to parallel the object model we need
in the code (and if we can omit the fact that emus and ostriches are
not in the same genus as penguins), THEN we might get an interface
which is easy to make a mistake in, early in its life where the
mistake is most costly.

--
Phlip at politizen dot com (address munged)
======= http://users.deltanet.com/~tegan/home.html =======

-- "When the man said alcohol, tobacco and firearms, I just
naturally assumed he was making a delivery." - -T --

Boris Schaefer

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to
Simon Harris <sha...@connect.net.au> writes:

| I just started reading this thread so please forgive me if it's been stated

| before or whatever. I see your point about expected behaviour but what would

| consider an appropriate solution for the following very theoretical example?
|

| class Plane {
| Vector cargo;
|
| addCargo();
| Vector getCargo();
| etc...
|
| takeOff() {

| if sum of the weight of all elements in cargo < 1000 {

| we can fly
| } else {
| too heavy!!!
| }
| }
| }

Another solution might be:

class plane {
public:
int max_cargo() { return /* maximum cargo */ }
int cargo() { return /* current cargo */ }
int remaining_cargo() { return max_cargo() - cargo(); }

bool add_cargo(cargo_type& item) {
if(remaining_cargo() < item.weight())
return false;
/* add the cargo */
return true;
}

void takeoff()
{
require(max_cargo() >= cargo());
/* take off */
}
};

In this case, you need to check whether add_cargo() failed, but not
takeoff(). If max_cargo() < cargo(), then require should throw an
exception, but this should never happen, if you add cargo only through
add_cargo().

I think this is a sane approach, because IMHO, excess cargo should be
handled at check in and not at take off. For take off adequate cargo
is a precondition and excess cargo an exception.

In general, I think failure conditions should be handled at the
earliest point possible.

--
Boris Schaefer <s...@psy.med.uni-muenchen.de>

Many pages make a thick book.

nsi5700000-Perryman

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to
"Robert C. Martin" <rma...@oma.com> writes:

> That's fine. I agree that spending time on getting the requirements right
> is a good thing to do. But it's also an activity that is doomed to fail.
> The requirements are simply never right; and even if they are then, just
> wait a minute.

See below.

> It is not outlandish to expect that the initial requirements writers simply
> never conceived of the notion of a flightless bird. They framed the
> requirements in such a way as to set that expectation amongst all the
> engineers as well. And so the Bird class was written with a postcondition
> that demanded flight. And many application programs, having seen that
> postcondition, wrote their code accordingly. Thus the introduction of
> flighless birds breaks an huge structure of software.

> How could we have prevented this? By employing the Interface Segregation
> Principle.

Nope.
It is not separation of interface that has caused the problem in our
little poser, it is "what is the concept of flight, and how do birds
achieve it ?? " , and its subsequent realisation in software as OOD/OOP.

Initial study of the problem may not have seen flightless birds, but then
correspondingly the developers may not have considered the notion of a
bird that temporarily cannot fly (broken wing, feather rot etc) . The
latter being a bird that can fly, that cannot fly.

If someone had just bothered to ask simple questions about flight and birds,
before rushing into a design, perhaps we would have all got the same
solutions. :-)

Who would now like to rework their initial offerings, given the statements
that we have birds like penguins, and birds that temporarily cannot fly ??

IMHO it would be interesting to see the 'deltas' that arise from changes
in requirement.

> The ISP is one of many such design principles that help us create software
> that is generally more susceptable to change.

Granted.
Unfortunately, the nature of our work means there are changes that no
design principle will effectively shield us from. One of the factors of
realising concepts in OO (OOA/OOD/OOP) is often having foresight and
experience.


Regards,
Steven Perryman

nsi5700000-Perryman

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to
"Robert C. Martin" <rma...@oma.com> writes:

> Consider, for example, class Modem:

> class Modem
> {
> public:
> virtual void Dial(char*) = 0;
> virtual void Hangup() = 0;
> virtual void Send(char) = 0;
> virtual char Recv() = 0;
> };

> Presume that hundreds of applications use the class. And that there are
> many different derivatives of it.

> Now presume that a new kind of modem appears (actually a very old kind of
> modem). A Cable modem. One that does not need to be dialled or hung up.

> We'd like all the programs that currently use Modem to be able to use
> CableModem, which means that CableModem must be a derivative of Modem. But
> CableModem does not have a reasonable implementation for Dial or Hangup.

> Proper abstract thinking might suggest that you alter the Modem hiearchy as
> follows:

> class Modem
> {
> public:
> virtual void Send(char) = 0;
> virtual char Recv() = 0;
> };

> class DiallingModem : public Modem
> {
> public:
> virtual void Dial(char*) = 0;
> virtual void Hangup() = 0;
> virtual void Send(char) = 0;
> virtual char Recv() = 0;
> }

> Then CableModem can derive from Modem and won't have to worry about Dial and
> Hangup. But if you do this, then all the applications that currently use
> modem must be recompiled and retested. This may be to great a price to pay
> for "correct abstraction".

The test would be whether regions of code can be restructured so that the
places where dial and hangup were used are reduced in number. And that
the code regions where send and recv are used now become usable in more
contexts than before.

That IMHO is the true test of whether refactoring an abstraction is
worth it at the design/programming level.

I usually start the refactoring process at the OOA level anyway, as it
doesn't impact code as such. Once I am sure the change in abstraction holds,
I then consider how the change impacts the design.

> In the end, software is engineering, and engineering is sometimes ugly.
> There are no perfect foolproof solutions to all problems.

Sadly. :-( :-)


Regards,
Steven Perryman

Simon Harris

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to
Agreed, the problem has moved to add_cargo(). We must now check when
adding the cargo instead of when flying.

There may be any number of reasons why the plane cannot take off. Just checking
that the cargo weight is not enough. What about oil pressure, fuel levels, etc.
etc. The only time I can really tell if all is ok is when we decided to fly and go
through the checklist.

I can open a socket, I can send a packet, but if someone puts a backhoe through my
cable, it isn't going to be sent even though I called send() and expected it to
go.

Boris Schaefer wrote:

--
Simon Harris

sha...@connect.net.au
Haruki...@yahoo.com

nsi5700000-Perryman

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to
Robert O'Dowd <nos...@nonexistant.com> writes:

> Robert C. Martin wrote:

> > Consider, for example, class Modem:

> > class Modem
> > {
> > public:
> > virtual void Dial(char*) = 0;
> > virtual void Hangup() = 0;
> > virtual void Send(char) = 0;
> > virtual char Recv() = 0;
> > };

> > Presume that hundreds of applications use the class. And that there are
> > many different derivatives of it.

> > Now presume that a new kind of modem appears (actually a very old kind of
> > modem). A Cable modem. One that does not need to be dialled or hung up.

> > We'd like all the programs that currently use Modem to be able to use
> > CableModem, which means that CableModem must be a derivative of Modem. But
> > CableModem does not have a reasonable implementation for Dial or Hangup.

> You were on a roll, but unfortunately picked a bad example.

> A reasonable implementation for Dial() and Hangup() is to do nothing.
> That would meet your requirement, and not break any code that only knows
> about the Modem class.

I disagree.
What 'digits' do I give to a cable modem for it to be able to ignore them ??
Somewhere a client has to produce a 'fake' digit sequence in order to feed
it to the dial operation.

This IMHO is the kind of code we do not want to be writing.

And as you state, tis indication that the model is broken and we need to
reorganise and redesign. The measure of your *existing* model is I guess
the degree of change you need to make in order to accommodate the new order.
We mostly hope the degree is not so great, but accept that sometimes
significant change may be neccessary.


Regards,
Steven Perryman

nsi5700000-Perryman

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to
"Robert C. Martin" <rma...@oma.com> writes:

> Encapsulation is the shielding of callers from the data/implementation
> within the server object. Encapsulation does not mean that all callers must
> assume that functions may be implemented as no-ops.

Encapsulation is the concept of pulling together data, and the operations
that act on that data, into one coherent entity (modules and packages in
Modula-2 and Ada, a class in OOP etc) .

The shielding aspect you speak of is called 'information hiding' .

The two concepts are often found together hand-in-hand, but this is not a
prerequisite for either to be used.


Regards,
Steven Perryman

Stefan Seefeld

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to
"Robert C. Martin" wrote:

> Consider, for example, class Modem:
>
> class Modem
> {
> public:
> virtual void Dial(char*) = 0;
> virtual void Hangup() = 0;
> virtual void Send(char) = 0;
> virtual char Recv() = 0;
> };
>
> Presume that hundreds of applications use the class. And that there are
> many different derivatives of it.
>
> Now presume that a new kind of modem appears (actually a very old kind of
> modem). A Cable modem. One that does not need to be dialled or hung up.
>
> We'd like all the programs that currently use Modem to be able to use
> CableModem, which means that CableModem must be a derivative of Modem. But
> CableModem does not have a reasonable implementation for Dial or Hangup.

I'd argue in terms of pre and post conditions. If Your postcondition is that
after having called Dial() the modem will be in a connected state, I think
it's plausible to make Dial() for modems which are always connected simply a
noop. Sure, ifwe would have known from the very beginning that such devices
exist the abstract design of modems would probably look different. But given
that at that point all modems needed a special method to set up the connection
I think it's valid.
However, I think the point with (non)flying birds is different. If you
stipulate that 'can fly' is a static requirement for birds (part of their
definition) you simply can't include penguins, at least not in statically
typed languages like C++.

Stefan
_______________________________________________________

Stefan Seefeld
Departement de Physique
Universite de Montreal
email: seef...@magellan.umontreal.ca

_______________________________________________________

...ich hab' noch einen Koffer in Berlin...

Ell

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to
"Robert C. Martin" <rma...@oma.com> writes:

#> How could we have prevented this? By employing the Interface Segregation
#> Principle.

This term adds no more value than the terms we used in OO for years -
"mixins", "orthogonal interfaces" and "aggregations". Why unnecessarily add
to the complexity?

Ell

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to
nsi5700000-Perryman <sp...@mlgw089.uk.lucent.com> wrote:

#"Robert C. Martin" <rma...@oma.com> writes:
#

#> Encapsulation is the shielding of callers from the data/implementation
#> within the server object. Encapsulation does not mean that all callers must
#> assume that functions may be implemented as no-ops.

Or more precisely, it means exposing an interface, while hiding implementation
- public interface, private implementation. Booch "encapsulation serves to
separate the contractual interface of an abstraction and its implementation".
[pg 513]

#Encapsulation is the concept of pulling together data, and the operations
#that act on that data, into one coherent entity (modules and packages in
#Modula-2 and Ada, a class in OOP etc) .

Typically in OO, this is abstraction. Classes and objects are means of
implementing such abstraction. Encapsulation is mainly about how classes and
objects expose an interface and hide implementation.

nsi5700000-Perryman

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to
univ...@radix.net (Ell) writes:

> nsi5700000-Perryman <sp...@mlgw089.uk.lucent.com> wrote:

> #Encapsulation is the concept of pulling together data, and the operations
> #that act on that data, into one coherent entity (modules and packages in
> #Modula-2 and Ada, a class in OOP etc) .

> Typically in OO, this is abstraction. Classes and objects are means of
> implementing such abstraction. Encapsulation is mainly about how classes and
> objects expose an interface and hide implementation.

This was the trap I suspected someone might fall into.

It is possible to support encapsulation, but not information hiding.
Examples of this are :

- the early Simula releases

- types in Ada and Modula-2 that are not defined to be 'private' or 'opaque'

- the 'private:' construct in C++ (although I cannot access these
definitions I can see them - and with some suitable nastiness could try
and get access to them) .


Examples of encapsulation *and* information hiding are :

- CLU
- Eiffel


The notion of abstraction IMHO is about trying to define and deal with
concepts without concerning ourselves with the *details* (implementation
etc) . Encapsulation and information hiding are techniques that aid us in
this goal.

Regards,
Steven Perryman

Simon Withers

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to

Ell wrote in message <37887e40...@news1.radix.net>...

>"Robert C. Martin" <rma...@oma.com> writes:
>
>#> How could we have prevented this? By employing the Interface Segregation
>#> Principle.
>
>This term adds no more value than the terms we used in OO for years -
>"mixins", "orthogonal interfaces" and "aggregations". Why unnecessarily
>add to the complexity?


It seems to me, and correct me if I'm wrong, that the ISP is a guideline
that tells us how to do something, while "mixins", "orthogonal interfaces"
and "aggregations" are descriptions of what can be done.

As in you follow the guidelines of the ISP by breaking your classes up into
a set of mixins, that each abstract a seperate and distinct portion of the
whole class.

The ISP adds value not be defining something new, but by recording a method.


Simon

Richard J. Botting

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to
Phlip at politizen dot com wrote
>Ell wrote:
>
>>>Richard J. Botting wrote in message
>
>
>>>#(B): All birds fly, all penguins are birds, penguins don't fly.

>>
>>So it wasn't your fantasy of someone creating an implementation
>design that
>>was too close to the domain and then the design being brittle as a
>result of
>>requested changes,
>
>Richard's sentence is internally self-contradictory.
>
>The very first clause represents a mismatch between real-world
>objects and their software counterparts. _Real_ software is
>frequently much more confusing than this simple example, so this
>kind of mistake can easily happen to those of us who are not
>unquestionably infallibly perfect.
>
>Richard's self-contradiction was short-hand to indicate we were
>expected to discuss the situation where the mistake had already been
>made.
>

One of the reasons I'm bothered by my "spec"
is that I could see that it refutes
one of my favorite dogma's: "Listen to
the user/client/expert." It seems to capture
a lot of "common sense logic" but *is*
definitely contradictory in standard
logics. There's been some research on
contradictory requirements recently....

It means that some how I'd have to teach people
to both seek out the user's ideas about the
world, and be willing to fight the users about those
ideas. Difficult. Horribly contradictory.

By the way... I think the problems don't really
start up until you tackle the Elephants...


rbotting at CSUSB edu
Software Development research and reviews

Richard J. Botting

unread,
Jul 7, 1999, 3:00:00 AM7/7/99
to
"Robert C. Martin" wrote

>>if a bird is told to fly, it may elect not to.
>
>I don't know who told you that, but it's quite a bizzare concept. Can you
>imagine a telling a stack to push a data item, and then having it elect not
>to?

Well this is precisely what has happened on many
of my programs over the years... starting with
an Ackerman function while testing a new language
in ICI HOC Division Redcar Yorkshire 1965.

In my experience most operations/methods
fail under some circumstances. Or in cleverer
terms:
Having exceptions is the rule:-)
This is a reason why I like JSP style backtracking...

It is loading more messages.
0 new messages