Maximum Accepted Layers/Levels of inheritance

P

Phlip

[CC'd to a design newsgroup, because the answer is not specific to C++]
What could be the maximum Accepted Layers/Levels of inheritance in a
normal C++ program that has private(data),protected(data) and
public(data,member functions) ?

Don't...

- design all your classes "up front" before
coding any of them
- use inheritance as a "compilable comment"
- use public data
- abuse protected data - use virtual accessors instead
On what does this depend upon ?

Good software...

- passes all automated tests
- is clear and readable
- duplicates no behavioral code
- minimizes lines, methods, and classes

Start with the book /Design Patterns/, generally to learn what the "point"
of all this OO stuff is. You will notice most patterns use only 2 layers of
inheritance - an abstract interfacial class, and concrete classes that
inherit from it. Too much inheritance is a "design smell", because
inheritance is one of the strongest forms of "coupling", and we should
instead want classes with carefully managed dependencies between things.
 
K

karthikbalaguru

Hi,

What could be the maximum Accepted Layers/Levels of inheritance in a
normal C++ program that has private(data),protected(data) and
public(data,member functions) ?
On what does this depend upon ?

Thx in advans,
Karthik Balaguru
 
K

Kai-Uwe Bux

karthikbalaguru said:
Hi,

What could be the maximum Accepted Layers/Levels of inheritance in a
normal C++ program that has private(data),protected(data) and
public(data,member functions) ?

a) If you are talking about coding guidelines, then any number could be your
superiors personal upper bound of acceptability:)

b) If you are talking about whether the compiler will accept your code, the
standard [Annex B] recommends that an implementation supports classes with
up to 16384 direct and indirect base classes. Whether you want to go that
far, is up to you.
On what does this depend upon ?

For (a), experience, taste, and local culture.

For (b), the implementation.


Best

Kai-Uwe Bux
 
J

James Kanze

- design all your classes "up front" before
coding any of them

But do design your classes to work together (which means
designing more than one at a time).
- use inheritance as a "compilable comment"

I'm not sure what you mean by this.
- use public data
- abuse protected data - use virtual accessors instead
Good software...
- passes all automated tests
- is clear and readable
- duplicates no behavioral code
- minimizes lines, methods, and classes

The last is, of course, in definite contradiction to the second
and the third. Avoiding code duplication will up the number of
functions and possibly the number of classes, and minimizing
lines doesn't always improve readability---if not, APL would be
the most readable language around.

But these are mostly secondary considerations, derived from the
two principle considerations: good software is functionally
correct (i.e. it's behavior conforms to the requirements
specification), and is easy to maintain. Automated tests are a
necessary (but not sufficient) condition of the first, and clear and
readable is a necessary (but not sufficient) condition of the
second. You're third and fourth points are generally minor
issues, and don't always hold.
Start with the book /Design Patterns/, generally to learn what
the "point" of all this OO stuff is. You will notice most
patterns use only 2 layers of inheritance - an abstract
interfacial class, and concrete classes that inherit from it.
Too much inheritance is a "design smell", because inheritance
is one of the strongest forms of "coupling", and we should
instead want classes with carefully managed dependencies
between things.

The key above is "most patterns". This is, in some ways,
something like the discussion on function length. There is no
absolute limit, but if most hierarchies end up with more than
two or three levels, it's a suspicious symptom, worth looking
into. I'd guess that about 90% of my hierarchies are just an
interface and a set of derived implementations: two levels.
Sometimes the derived implementations will use the template
method patter, which bumps it up to three, and sometimes it
makes sense to insert an abstract level between the interface
and the implemention, with some commonly needed convenience
functions. And some very special patterns, like mixin, do
involve more (four or five levels), but they are fairly rare.

Note too that the can be technical reasons for using C++
inheritance, unrelated to the design issues. It's not rare for
generated code to double the number of levels, and I've seen
cases in low level code where inheritance was used instead of
containment in order to allow empty base class optimization
(often the case for the allocators in the standard library), or
for reasons of exception safety (i.e. to allow "partial"
destruction of a partially constructed object). I would not
count either of these in the designed depth.
 
H

H. S. Lahman

Responding to Phlip...

Mostly for the OP's benefit...
Good software...

- passes all automated tests

Using testing as a product reliability gate only works to something
approaching 5-Sigma. Even then it is only a necessary condition, not a
sufficient condition.

Beyond 5-Sigma testing must monitor the development process rather than
the product. In that situation one needs to test until enough tests fail
to provide a reasonable statistical sampling. IOW, the testing is
inadequate unless enough tests fail. (Obviously if one knows a defect
exists after monitoring the process, one usually wants to fix it prior
to customer shipment but that is a different process stage once one has
monitored the process with testing.)
- is clear and readable
- duplicates no behavioral code

Normal Form applies to knowledge attributes as well as behavior
responsibilities.
- minimizes lines, methods, and classes

This is only relevant in 3GL development. In a translation environment
one never even looks at the 3GL code so none of this matters. In fact,
at the 4GL level all dependency management refactoring is irrelevant
because there is none of the physical coupling present in 3GL type systems.

If one is developing in an elaboration environment, then dependency
management becomes critical to provide 3GL maintainability due to
physical coupling in the 3GLs. However, that is solving a developer
problem rather than the customer problem. So one Good Software also
requires avoiding architectural drift during the dependency management
refactoring.


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
(e-mail address removed)
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
(e-mail address removed) for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
 
P

Phlip

James said:
I'm not sure what you mean by this.

penguin --|> flighted_bird

This great post by Tim Ottinger says it best:

----8<-------------------------------
But I find it amusing and odd that you so frequently beat the drum about
the need to "generalize" as if most programmers don't learn this in
Programming 101. Generalization is like mother's milk to most programmers.

No, though it ought to be. We're trying to make it so. But in most shops
where I've worked before OMA, most shops my friends work at, many
places where we're brought in to teach, and the majority of shops where
other teachers I know teach, abstraction is not understood in the light of
dependency management and risk management. It's just a way of taking a wild
swag at the structure of a system.

Inheritance is typically used too much, and usually wrongly. It's most
frequently used to denote inappropriate dynamic set memberships, create
inappropriate couplings of orthogonal aspects of objects, or even worse as
"compilable comments".

If most programmers are taught how to do this in Intro To Programming 101,
then 101 needs a serious reworking. For details of how people typically do
this incorrectly, read "What Every Programmer Should Know About OOD",
chapters 8-12.
 
P

Phlip

James said:
The last is, of course, in definite contradiction to the second
and the third.  Avoiding code duplication will up the number of
functions

That's why I was very careful to say "behavioral" code. That's generally the
live executable statements that add value. A subset of the statements that
(in C++) you could put a debugging breakpoint on.
 
P

Phlip

James said:
The key above is "most patterns".

I wrote that because I didn't have /DP/ to hand. I suspect that no pattern
in /DP/ has more than two layers, and I suspect this answers the OP. 2.
 
P

Phlip

Juha said:
Eschew obfuscation, though.

The 0th item in my list (before the T-word) is "follows your community
agreements". That means you only write what you know your team can read.

The rules are applied in order, as if you did all of one of them before
doing the next. You should not regress. So you should not resolve
duplication in a way that impairs your community agreements.
Also, minimizing the number of classes should not be made by creating
http://en.wikipedia.org/wiki/God_object

And if you apply "lines, methods, and classes" from left to right, God
Objects typically don't minimize methods or lines...
 
D

Daniel T.

Phlip said:
[CC'd to a design newsgroup, because the answer is not specific to C++]
What could be the maximum Accepted Layers/Levels of inheritance in a
normal C++ program that has private(data),protected(data) and
public(data,member functions) ?

I don't think there should be any maximum number of "accepted layers".
Make it as deep as you want. However, I think that all base classes in
C++ should contain only pure-virtual functions and no data (i.e.,
interface classes.)
 
P

Phlip

Daniel said:
Make it as deep as you want.

You can't mean that. What's the deepest real-code example you have
available?

Note that our arch-enemy here is junior programmers writing

Hylidae --|> Anura --|> Amphibia --|> Chordata --|> Animalia

for no other reason than the target program contains taxa of tree-frogs. The
correct implementation would probably be the Composite Design Pattern,
which has 2 levels of inheritance.
However, I think that all base classes in
C++ should contain only pure-virtual functions and no data (i.e.,
interface classes.)

Isn't this putting the cart before the horse? What if, for some given
real-code situation, a few implementations should be in the base class.
Yes, that's just a convenience. Moving them down, at refactor time, should
be trivial. So just leave them there. The other arch-enemy here is junior
programmers behaving as if designs must never be changed...
 
D

Daniel T.

Phlip said:
You can't mean that. What's the deepest real-code example you have
available?

Note that our arch-enemy here is junior programmers writing

Hylidae --|> Anura --|> Amphibia --|> Chordata --|> Animalia

for no other reason than the target program contains taxa of tree-frogs. The
correct implementation would probably be the Composite Design Pattern,
which has 2 levels of inheritance.


Isn't this putting the cart before the horse? What if, for some given
real-code situation, a few implementations should be in the base class.
Yes, that's just a convenience. Moving them down, at refactor time, should
be trivial. So just leave them there. The other arch-enemy here is junior
programmers behaving as if designs must never be changed...

To answer your questions, I've been known to write hierarchies 3-4
levels deep, and no I don't think it's putting the cart before the horse.

I have encountered no "real code situation" where "a few implementations
should be in the base class" in C++. Rather if several derived classes
all share common data and methods, I put them in a delegate class that
they all use. There is no "moving them down" because they were never in
the base class to begin with. (Note: my answer is different in a dynamic
typed language like Python.)

As for your tree-frog example. If I have a class that uses the Chordata
class and passes its Chordata objects to a class that uses the Amphibia
class and it passes objects to a class that uses the Anura class and it
passes them to a class that uses the Hylidae class, then I see nothing
wrong with the hierarchy as you have presented it. Such a situation is
obviously very rare. Maybe twice in the past 10 years.
 
P

Phlip

Daniel said:
To answer your questions, I've been known to write hierarchies 3-4
levels deep, and no I don't think it's putting the cart before the horse.

The ideal "no implementation in base classes" struck me as a "prosthetic
goal". Switching metaphors here. The real goals should be code that passes
all tests, is clear and expressive, duplicates no behavior, etc. You will
probably get that, whatever the language, with lean base classes.
I have encountered no "real code situation" where "a few implementations
should be in the base class" in C++. Rather if several derived classes
all share common data and methods, I put them in a delegate class that
they all use.

Right, but the motivation was separating concerns.
As for your tree-frog example. If I have a class that uses the Chordata
class and passes its Chordata objects to a class that uses the Amphibia
class and it passes objects to a class that uses the Anura class and it
passes them to a class that uses the Hylidae class, then I see nothing
wrong with the hierarchy as you have presented it. Such a situation is
obviously very rare. Maybe twice in the past 10 years.

Yet you did not start your design by declaring that a proposed Amphibia
object should inherit a proposed Chordata object, based entirely on their
names. You probably found a real virtual method for the concrete classes to
implement, in a way that folded together duplicate behaviors. And twice in
10 years confirms our "Zipf's Law" hunch that the answer to the OP's
question is still "almost always 2!"
 
J

James Kanze

I wrote that because I didn't have /DP/ to hand. I suspect
that no pattern in /DP/ has more than two layers, and I
suspect this answers the OP. 2.

I suspect that as well, although I once used a pattern which
required 4. (It was a very special case, however, and related
to the fact that we were generating much of the code
automatically.) And mixins require a minimum of 3, and 4 often
makes them clearer. What I was really meant to get at, however,
is that when you compose patterns, you will often end up with 3
or maybe 4 levels. It's not a problem *IF* the patterns
involved and the composition is clear.
 
J

James Kanze

[Google won't show me the original posting you are
responding to, so I'll comment it here.]
Make it as deep as you have to. In many ways, the shallower the
better, but of course, there are more important criteria which
may push the level up a little. I'd be suspicious if, in an
application, more than about 5% of the hierarchies were deeper
than 3, and I'd probably at least glance at those hierarchies,
just to make sure that the depth was justified.

Which ignores some important patterns, like the template method
pattern. Also, of course, in C++, it's fairly rare for public
functions to be virtual; the usual idiom is to use public
functions to specify the interface, including the contract (pre-
and post-conditions), and have them forward to private virtual
functions for the implementation details.
Isn't this putting the cart before the horse?

Totally agreed. We define a solution, without having specified
a problem.
What if, for some given real-code situation, a few
implementations should be in the base class. Yes, that's just
a convenience. Moving them down, at refactor time, should be
trivial. So just leave them there.

If the contract says that they must have a specific interface,
then yes, they belong in the base class. If it's just a
convenience, it's often preferable to put them in an
intermediate class, if only to reduce compilation dependencies
(but I find that it helps readability as well). At any case,
it's a judgement call.
The other arch-enemy here is junior
programmers behaving as if designs must never be changed...

On large projects, a junior programmer doesn't have the right to
change the design himself. At least not if the change involves
changes in the interface, which breaks other peoples code. (But
I agree with the thrust of your comment. Just replace "junior
programmers" with "pointy-haired managers".)
 
P

Phlip

James said:
(But
I agree with the thrust of your comment.  Just replace "junior
programmers" with "pointy-haired managers".)

/Don't/ try to make friends with me! (-;
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,146
Messages
2,570,832
Members
47,374
Latest member
anuragag27

Latest Threads

Top