Inheritance-Based Design


Inheritance Vs. Composition: The Great Debate

A continuing debate simmers amongst object-oriented design theorists and practitioners. I draw your attention to this debate only because sooner or later you will encounter it either in person or by reading the existing literature.

Each group is philosophically divided into three camps: 1) the compositionists, and 2) the inheritists, and 3) the design pragmatists. The issue revolves around the answer to this question: “What is the preferred approach to achieving code reuse within an object-oriented program?”

The compositionists are the most radical. They believe the only way to achieve code reuse within a program is through compositional design. All forms of inheritance as a reuse mechanism are suspect and should be avoided. They believe that anyone who advocates inheritance as a code reuse mechanism is a heretic and would be better burned at the stake than left free to wreak havoc on object-oriented programs.

The inheritists believe that inheritance is a valid form of code reuse. They know of the term composition but in their textbooks they provide the topic only superficial treatment at best. I get the distinct impression that an inheritist has little or no practical experience in object-oriented programming in a production environment.

The design pragmatists understand the meaning of the term “ engineering trade-off ”. They use inheritance where it makes sense to do so and composition when the situation dictates. They understand what it means to compromise in order to make progress while at the same time taking every practical measure to ensure design goals are achieved. They realize several important facts of life: 1) the world in which we live may be perfect but human understanding of the world is decidedly imperfect, 2) it follows, then, that a human-derived model of any problem will suffer from imperfection, 3) mapping an imperfect model to a object-oriented design results in an imperfect design, 4) mapping the imperfect design to an implementation results in an imperfect implementation, and 5) Java itself suffers from imperfection, as do all object-oriented programming languages humans create. Therefore — to argue perfect design is a waste of time.

Design pragmatists will attempt to achieve reuse at very step. My use of the term reuse here includes reuse in all its forms. (code, design, knowledge, etc.) To a design pragmatist, therefore, the issue is not strictly reuse, but rather what amount of design is appropriate to the task at hand.

What’s The End Game?

Most programming endeavors are business ventures; there’s a very real need to make a profit. If every class in every system had to be reusable in every context all projects would end in failure. So, the right amount of design depends on system requirements. Not functional requirements, per se, but implicit requirements common to all object-oriented application architectures that concern ease of maintenance, modularity, flexibility, and reusability.

Theorists can afford to argue design aesthetics all the live-long day, but production coders cannot. They must deliver the goods on what always seems to be a tight schedule. Production programmers continuously make engineering trade-offs. So long as they are not completely off target design-wise they’ll make their schedule with a product that’s not impossible to maintain.

I take the metaphor of “hitting the design target” literally. On a recent project I gathered my developers around a white board periodically to conduct a design gut check. I’d draw a target on the board complete with a bullseye in the center. I would then ask each developer to place a mark on the target indicating where they thought our design and coding efforts placed us, with the bullseye representing 100% perfection. We never hit the bullseye; no project ever will. What mattered most was that we did not completely miss the target, and that with each iteration we moved closer to the bullseye.

Since hitting the design bullseye on your first shot is unlikely, what then are you trying to achieve in terms of design and implementation? The answer is simply that you don’t want to program yourself into a corner from which there is no escape. Your application architecture must be flexible so that it can accommodate change, it must be modular and reliable, and it must be stable.

Flexible Application Architectures

Application architectures must be flexible enough to accommodate anticipated feature additions gracefully. Graceful change accommodation is an application requirement, and in most cases this application requirement is not explicitly stated. To achieve this requirement you must have it in mind at the start of your design, otherwise you will end up eroding the application’s architectural foundation as you try and shoehorn new features into the application.

Modularity And Reliability

Application components must be both modular and reliable. In an object-oriented application the natural boundary for modularity is at the class level. A class represents an abstraction of a problem domain entity. If you do a good job at maximizing class cohesion (i.e., give the class a focused purpose) and minimizing class coupling (i.e., limiting its dependency on other classes) you will find it significantly easier to reuse such classes.

Reliability does not necessarily follow from modularity, and indeed, code reliability depends upon a multitude of factors, but well-designed classes (i.e., maximally cohesive and minimally coupled) lend themselves to more thorough testing. A class that is designed to serve one purpose and that is reused in many locations within an application will tend to have its behavior exhaustively tested.

Architectural Stability Via Managed Dependencies

Application architectures must be stable. This means that a change to the application must have predictable results. The ability to correctly anticipate the effects that change will have on an application varies inversely with the number of inter-module dependencies. The more dependencies, the harder it is to anticipate the effects of change.

Knowing When To Accept A Design That’s Good Enough

The answer to the question of when a design has reached “good enough” is always the same — it depends. It depends on the application’s intended purpose and its associated requirements. The decision is usually made in the context of time spent making the application architecture completely generic (never a requirement I’ve personally encountered) vs. crafting an architecture that’s flexible enough to accommodate contextually similar feature additions.

Quick Review

There are three philosophical camps regarding the attainment of code reuse within an application: compositionists, inheritists, and design pragmatists. Design pragmatists utilize the full spectrum of object-oriented design mechanisms to achieve reuse on all possible fronts. Their approach to design is rooted in making intelligent engineering trade-offs.

The end game of good design is an application architecture that is flexible, modular, reliable, and stable.




Java For Artists(c) The Art, Philosophy, and Science of Object-Oriented Programming
Java For Artists: The Art, Philosophy, And Science Of Object-Oriented Programming
ISBN: 1932504052
EAN: 2147483647
Year: 2007
Pages: 452

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net