Is it possible to boil everything down to 10? If we could
then we could pass this on to students and people just getting
into OO and it might save some time for everyone.
Here is a sample list, not necessarily in order.
1. Class names should be nouns and method names should be verbs.
2. Comments are as important as the code and should be "classified".
3. Inheritance should help new developers "program the differences".
4. Encapsulation of everything, including source, is essential.
5. Verbs (methods) should be chosen carefully to support polymorphism.
6. Reuse can only be acheived when standard classes are understandable.
7. New developers should reuse the CASE tools of the experienced developers.
8. Cross-platform portability should take priority over other design issues.
9. A new class should be used for each new legacy library interface.
10.Multiple returns should be used for error handling and debugging only.
--
Jim Fleming /|\ Unir Corporation Unir Technology, Inc.
%Techno Cat I / | \ One Naperville Plaza 184 Shuman Blvd. #100
Penn's Landing / | \ Naperville, IL 60563 Naperville, IL 60563
East End, Tortola |____|___\ 1-708-505-5801 1-800-222-UNIR(8647)
British Virgin Islands__|______ 1-708-305-3277 (FAX) 1-708-305-0600
\__/-------\__/ e-mail: jim.f...@bytes.com
Smooth Sailing on Cruising C+@amarans
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\______to the end of the OuterNet
1. Software entities (classes, modules, etc) should be open for
extension, but closed for modification. (The open/closed
principle -- Bertrand Meyer)
2. Derived classes must usable through the base class interface
without the need for the user to know the difference. (The
Liskov Substitution Principle)
3. Details should depend upon abstractions. Abstractions should
not depend upon details. (Principle of Dependency Inversion)
4. The granule of reuse is the same as the granule of release.
Only components that are released through a tracking system can
be effectively reused.
5. Classes within a released component should share common closure.
That is, if one needs to be changed, they all are likely to need
to be changed. What affects one, affects all.
6. Classes within a released componen should be reused together.
That is, it is impossible to separate the components from each
other in order to reuse less than the total.
7. The dependency structure for released components must be a DAG.
There can be no cycles.
8. Dependencies between released components must run in the
direction of stability. The dependee must be more stable than
the depender.
9. The more stable a released component is, the more it must
consist of abstract classes. A completely stable component
should consist of nothing but abstract classes.
10. Where possible, use proven patterns to solve design problems.
11. When crossing between two different paradigms, build an
interface layer that separates the two. Don't pollute one side
with the paradigm of the other.
--
Robert Martin | Design Consulting | Training courses offered:
Object Mentor Assoc.| rma...@rcmcon.com | Object Oriented Analysis
2080 Cranbrook Rd. | Tel: (708) 918-1004 | Object Oriented Design
Green Oaks IL 60048 | Fax: (708) 918-1023 | C++
> 9. The more stable a released component is, the more it must
> consist of abstract classes. A completely stable component
> should consist of nothing but abstract classes.
How can it do anything then?
People really do need actual answers once the rubber
meets the road. Look at Numerical Recipes {now the single most cited
work in non-biological sciences}. Full of down-to-the-details concrete
pieces. No larger-scale software organization, but it gives results.
> --
> Robert Martin | Design Consulting | Training courses offered:
> Object Mentor Assoc.| rma...@rcmcon.com | Object Oriented Analysis
> 2080 Cranbrook Rd. | Tel: (708) 918-1004 | Object Oriented Design
> Green Oaks IL 60048 | Fax: (708) 918-1023 | C++
matt
> If I had to write commandments, these would be candidates.
>...
> 9. The more stable a released component is, the more it must
> consist of abstract classes. A completely stable component
> should consist of nothing but abstract classes.
I'm curious as to how can anything be known to be completely stable? As
the domain or space changes abstractions may have to change.
Elliott
>If I had to write commandments, these would be candidates.
> 2. Derived classes must usable through the base class interface
> without the need for the user to know the difference. (The
> Liskov Substitution Principle)
Sorry to beat this issue to death, but...
How can you reconcile this commandment, with which I agree, with the discussion
that we had earlier about covariant method arguments. Clearly the derived class
does not satisfy the LSP since its methods will not accept all arguments that
the base class' methods would have accepted.
MV
Martin Vuille | "Your partner in | System Development Consulting
ProControl | successful product | Software & Firmware Contracting
(613) 258-0021 | development" | System Integration
and matt (m...@jt3ws1.seas.ucla.edu) wrote:
: People really do need actual answers once the rubber
: meets the road. Look at Numerical Recipes {now the single most cited
: work in non-biological sciences}. Full of down-to-the-details concrete
: pieces. No larger-scale software organization, but it gives results.
Looks like we have a long way to go before we can agree on the goals,
much less the solutions.
--
Shawn Willden
sh...@btsslc.com
swil...@icarus.weber.edu
>Robert Martin (rma...@rcmcon.com) wrote:
>> {lots of good ideas}
>> 9. The more stable a released component is, the more it must
>> consist of abstract classes. A completely stable component
>> should consist of nothing but abstract classes.
>How can it do anything then?
Abstract classes can have a lot of code in them! A class is abstract
iff it has at least one pure interface. The great thing about an
abstract class is that you can place a lot of high level policy code
into it. This is the code that "never changes", and then have that
code call virtual functions of its own class, or classes connected to
it. These virtual functions represent the "changeable" parts of the
application.
Thus, the abstract class can do *alot*, yet still be stable.
>In <1995Mar16.1...@rcmcon.com> rma...@rcmcon.com writes:
Obviously it can't. But you can estimate stability with a fair amount
of accuracy. Consider:
class Shape { public: virtual void Draw() = 0;};
void DrawShapes(Set<Shape*>& s)
{
for (Iterator<Shape*> i(s); i; i++)
(*i)->Draw();
};
This function is extremely stable. Yes, you can dream up scenarios
that might cause it to change, but for most applications that draw
shapes, they would be unlikely.
The idea is to find these abstractions that have high stability, and
then to force your dependencies towards them. Yes, it is a bit of a
risk, since you may decide wrong; but that is better than not trying.
>In article <1995Mar16.1...@rcmcon.com> rma...@rcmcon.com (Robert Martin) writes:
>>If I had to write commandments, these would be candidates.
>> 2. Derived classes must usable through the base class interface
>> without the need for the user to know the difference. (The
>> Liskov Substitution Principle)
>Sorry to beat this issue to death, but...
>How can you reconcile this commandment, with which I agree, with the
>discussion that we had earlier about covariant method arguments.
>Clearly the derived class does not satisfy the LSP since its methods
>will not accept all arguments that the base class' methods would have
>accepted.
In C++ this is not an issue since there are no covariant method
arguments. Covariance is simulated within the function using
dynamic_cast. i.e. the function checks that the type of the
incomming object is what it really expects, and throws an error if it
isn't. One can argue about whether this is LSP compliant or not. If
the error condition is designed in so that all users expect it, then
LSP has been satisfied. If there are users that will be confused by
the error, then LSP is violated.
But if we consider covariance to violate LSP, and if we admit that
covariance is needed at times, then we have a problem. The
commandements (just as in real life) are sometimes hard to keep.
When I started this thread...at least I think I started it...
I thought that I clearly labeled the subject "of OO Programming"...
I agree that the 10 items I proposed as a starting point (now lost
to the genecide surgeons)...are very programming specific...Maybe
we can develop a more general set for OOA and OOD. Maybe one set
for each.
I appreciate you comments. You will have to bear with me, I come
from the school of thought which says, "When in doubt, write code".
I know this sounds bad but, "code talks and all else walks..in the end".
Here are templates for OOA and OOD....anyone care to take the keyboard?
The Ten Commandments of OOA
---------------------------
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
The Ten Commandments of OOD
---------------------------
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
A common source of instability for such functions is the addition
of new features and/or parameters. For example, where does the
shape get drawn by Draw()? In a window? On a screen? What if
you add a second window or a new screen and need to be able to tell
Draw() where you want to it to draw the shape, instead of having it
use a built-in assumption about that?
I did not understand this either. Unfortunately, our news server deleted
all of comp.object and is currently "rebuilding". Because of the limited
"context" above, it is hard to rebuild all of the original info.
> Robert Martin <rma...@rcmcon.com> wrote:
> >
> >Obviously it can't. But you can estimate stability with a fair amount
> >of accuracy. Consider:
> >
> >class Shape { public: virtual void Draw() = 0;};
> >
> >void DrawShapes(Set<Shape*>& s)
> >{
> > for (Iterator<Shape*> i(s); i; i++)
> > (*i)->Draw();
> >};
> >
> A common source of instability for such functions is the addition
> of new features and/or parameters. For example, where does the
> shape get drawn by Draw()? In a window? On a screen? What if
> you add a second window or a new screen and need to be able to tell
> Draw() where you want to it to draw the shape, instead of having it
> use a built-in assumption about that?
To ensure stability in the face of these requirements you have to
abstract from the details of how a shape is output. Define an
abstract class Port that defines abstract output primitives.
Subclasses can then implement these primitives for different
devices. Based on an abstract class Port DrawShapes becomes:
void DrawShapes(Port& p, Set<Shape*>& s)
{
for (Iterator<Shape*> i(s); i; i++)
(*i)->Draw(p);
}
Now the commandment "a completely stable component should consist
of nothing but abstract classes" is fullfilled again.
BTW, this is not theorizing but this is how it is done in ET++.
This is the corresponding Port class hierarchy.
Port // abstract output device
WindowPort // window output devices
XWindowPort // an X window
MacWindowPort // a Macintosh window
SunWindowPort // Sunwindow window...
PrinterPort // printer output devices
PostScriptPort
With this Port hierarchy DrawShapes is stable
on different window systems and printers.
--erich gamma
ga...@taligent.com
My point was not that the original poster should have taken the port
into account, but rather that new features and parameters often have
to be added to such functions. It's impossible to take everything
into account in the original design, because experience using the
original design is needed before the additional requirements become
visible.
We can probably think of new requirements endlessly, but that would
be a slow and tedious way to prove that the function is not stable.
One example might be color, and another might be a need to have
the shapes sorted by size or coordinates before drawing them. 1000
others might be mentioned, but in real life, the actual requirement
that might pop up at some random time might not be in that list of
1000.
> >class Shape { public: virtual void Draw() = 0;};
> >
> >void DrawShapes(Set<Shape*>& s)
> >{
> > for (Iterator<Shape*> i(s); i; i++)
> > (*i)->Draw();
> >};
> >
> A common source of instability for such functions is the addition
> of new features and/or parameters. For example, where does the
> shape get drawn by Draw()? In a window? On a screen? What if
> you add a second window or a new screen and need to be able to tell
> Draw() where you want to it to draw the shape, instead of having it
> use a built-in assumption about that?
To ensure stability in the face of these requirements you have to
abstract from the details of how a shape is output. Define an
abstract class Port that defines abstract output primitives.
Subclasses can then implement these primitives for different
devices. Based on an abstract class Port DrawShapes becomes:
void DrawShapes(Port& p, Set<Shape*>& s)
{
for (Iterator<Shape*> i(s); i; i++)
(*i)->Draw(p);
}
Now the commandment "a completely stable component should consist
of nothing but abstract classes" is fullfilled again.
But now say we want to draw shapes on a given port using a given
geometrical transformation, as we do in ILOG VIEWS. Once again the
component is not completely stable. In fact, I don't see how you can
ever know when a specification is stable unless the component stops
being used altogether.
void DrawShapes(Set<Shape*>& s, Port& p, Transformer& t)
{
for (Iterator<Shape*> i(s); i; i++)
(*i)->Draw(p, t);
}
-- Harley Davis
--
------------------------------------------------------------------------------
Harley Davis net: da...@ilog.fr
ILOG S.A. tel: +33 1 46 63 66 66
2 Avenue GalliƩni, BP 85 fax: +33 1 46 63 15 82
94253 Gentilly Cedex, France url: http://www.ilog.com/
> But now say we want to draw shapes on a given port using a given
> geometrical transformation, as we do in ILOG VIEWS. Once again the
> component is not completely stable. In fact, I don't see how you can
> ever know when a specification is stable unless the component stops
> being used altogether.
Because the space, doamin, and world generally, change even the
abstractions of it may change. The best we can says is that abstractions
are relatively stable for now.
Elliott
> mvu...@procntrl.synapse.net (Martin Vuille) writes:
> >(Robert Martin) writes:
> >>If I had to write commandments, these would be candidates.
> >> 2. Derived classes must usable through the base class interface
> >> without the need for the user to know the difference. (The
> >> Liskov Substitution Principle)
> >Sorry to beat this issue to death, but...
> >How can you reconcile this commandment, with which I agree, with the
> >discussion that we had earlier about covariant method arguments.
> >Clearly the derived class does not satisfy the LSP since its methods
> >will not accept all arguments that the base class' methods would have
> >accepted.
> In C++ this is not an issue since there are no covariant method
> arguments. Covariance is simulated within the function using
> dynamic_cast. i.e. the function checks that the type of the
> incomming object is what it really expects, and throws an error if it
> isn't. One can argue about whether this is LSP compliant or not. If
> the error condition is designed in so that all users expect it, then
> LSP has been satisfied. If there are users that will be confused by
> the error, then LSP is violated.
>
> But if we consider covariance to violate LSP, and if we admit that
> covariance is needed at times, then we have a problem. The
> commandements (just as in real life) are sometimes hard to keep.
Hear, hear! The real world and software developement does not always
follow simple straight line logic or development.
Elliott
>But now say we want to draw shapes on a given port using a given
>geometrical transformation, as we do in ILOG VIEWS. Once again the
>component is not completely stable. In fact, I don't see how you can
>ever know when a specification is stable unless the component stops
>being used altogether.
>void DrawShapes(Set<Shape*>& s, Port& p, Transformer& t)
>{
> for (Iterator<Shape*> i(s); i; i++)
> (*i)->Draw(p, t);
>}
Quite right. Nothing can every be completely stable. However, there
are degrees of stability. And there are engineering options that you
can make that will increase or decrease the stability of an entity.
Where possible you want to have the dependencies between your modules
running in the direction of increasing stability.
This seems to be confusing the logical and physical models somewhat. I
would say the granule of reuse is a logical model entity, but the
granule of release is part of the physical model.
Or, more concretely, the granule of reuse is the class category and the
granule of release is the subsystem. Very often, these two entities
will be identical, but they need not be.
-- Bob
>My point was not that the original poster should have taken the port
>into account, but rather that new features and parameters often have
>to be added to such functions. It's impossible to take everything
>into account in the original design, because experience using the
>original design is needed before the additional requirements become
>visible.
>We can probably think of new requirements endlessly, but that would
>be a slow and tedious way to prove that the function is not stable.
>One example might be color, and another might be a need to have
>the shapes sorted by size or coordinates before drawing them. 1000
>others might be mentioned, but in real life, the actual requirement
>that might pop up at some random time might not be in that list of
>1000.
Quite right. But what is your ultimate point? Are you suggesting
that since complete stability is impossible, we should not strive for
any kind of stability? I think that we should strive to organize our
classes and categories so that they are as stable as we can make them.