Introduction
OO Design is more than just using an OO language. Over the years many bright
programmers have built up a collection of rules that help to build well designed
maintainable code. This article lists the main rules of OO programming. The
intention is to inspire the reader to think about these rules and make further
reading. There is a lot of material on the web that drills down into more
details with plenty of examples.
Ivar Jacabson said �All systems change during their life cycles. This must be
borne in mind when developing systems expected to last longer than the first
version�. In other words software requirements change with time. The goal of
Object Orientated Design is to program in such a way that such changes to the
software are predictable and do not make a large impact in the program. In other
words it should be stable in the presence of change
Bad design is characterized by
- A single change affects many other parts of the system (Rigidity)
- A single change affects unexpected parts of the system (Fragility)
- It is hard to reuse in another application (Immobility)
The Dependency Inversion Principle DIP
Imagine you have a simple database program. You don�t want to change the
entire application when changing the database. This principle is targeted at
removing such unwanted interdependency that can cause a design to be fragile.
The rule states
- High level modules should not depend upon low level modules. Both should
depend upon abstractions
- Abstractions should not depend upon details. Details should depend upon
abstractions
Booch said �All well structured object orientated architectures have
clearly-defined layers with each layer providing some coherent set of services
through a well defined and controlled interface.�
In other words design applications in layers where high level layers call
lower level layers using Abstract interfaces. To conform to the principle of
dependency inversion, we must isolate abstraction from the details of the
problem. Then we must direct the dependencies of the design upon the
abstractions.
Good dependencies are extremely unlikely to change. In other words they are
stable. We would like to base our architectural design around stable,
non-volatile modules.
The Open-Close Principle OCP
Software entities (Classes, Modules, functions etc) should be open for
extension, but closed for modification
In other words design classes that never change. When a new requirements come
add new code and don�t edit existing code. It is not possible to close against
all possible changes. Therefore an experienced developer needs to understand the
possible future wishes of users in order to make Strategic Closure. There are
two ways of closure:
- Using Abstraction to gain explicit closure - This means the programmer
applies abstraction to those parts of the programmer the designer feels are
subject to change.
- Using Data Driven Approach to achieve closure
Liskov Substitution Principle LSP
Every function that operates upon a reference or pointer to a base class
should be able to operate on derivatives of that base class without knowing it.
This means that virtual member functions of the derived class must expect only
all the corresponding member functions of the base class. In other words any
function that uses a base class must not be confused when a derived class is
substituted for the base class
This is a difficult principle to apply. To conform avoid overwriting base
class functions because this involves programming with details, instead try to
program in abstractions
If this is violated then functions that operate on the pointers must first
check the type of the actual object in order to work correctly.
Heuristics and Conventions
Make all member variables Private: Otherwise no function that calls the class
can be closed to change. For example a status variable can change from Boolean
to an enumeration, if this is not handled as a property then we cannot close
status. This is called encapsulation.
No Global Variables. Because misbehaving modules may write erroneous data
to such global variables whose effect can be felt in many places throughout the
program. Sometimes Global variables are useful e.g. cout
and cin
in c++. If they
do not violate the open close principle then sometimes they are worth the style
violation
Stability Dependencies Principle SDP
The dependencies between packages in a design should be in the direction of
stability of the packages. A package should only depend upon packages that are
more stable than it is.
Some volatility is necessary if the design is to be maintained. This is
achieved by using the Common Closure Principle, in this way we design packages
to be volatile and we expect them to change. Any package that we expect to be
volatile should not be depended upon by a package that is difficult to
change.
Some things we don�t want to change. For example architectural decisions
should be stable and not at all volatile. Therefore classes that encapsulate the
high level design should be stable.
The stable Abstractions Principle SAP
Packages that are maximally stable should be maximally abstract. Instable
packages should be concrete. The abstraction of a package should be in
proportion to it's stability
Common Reuse Principle CRP
If you reuse one class of a package, you reuse them all. This because any
package delivered contains a released set of classes, therefore a change in any
class means a new release of the entire package.
The Reuse / Release Equivalence Principle REP
The granule of reuse is the granule of release. Only components that are
released through a tracking system can be effectively reused. This principle is
important when there are several teams working on an application. To avoid one
team disrupting another all packages used are tested and released. In this way
the introduction of modified packages is in a controlled way.
The Common Closure Principle CCP
The classes in a package should be closed together against the same kinds of
changes. Any change in a package affects all classes in the package. Just like a
well organized team has a common goal because they all have to work together.
This principle means that you should have a common strategic closure concept
used through all classes in a package because they have to be released all
together.
The stability Dependencies Principle SDP
The dependencies between packages in a design should be in the direction of
stability of the packages. A package should only depend upon packages that are
more stable than it is.
Designs that are highly interdependent tend to be rigid, not reusable and
hard to maintain
The Acyclic Dependencies Principle (ADP)
The dependency structure between package must be a Direct Acyclic Graph
(DAG). This means that if you plot out all packages it should be possible to
arrange the dependencies to always point from top to bottom. Also it should not
be possible to follow any lines of dependence and end up back at the same
package. Because such packages would have to be released all at the same time
defeating the object of having them as separate packages
The Interface Segregation Principle ISP
Clients should not be forced to depend upon interfaces that they don�t
use.
This principle deals with the disadvantages of fat interfaces. Fat interfaces
are not cohesive. In other words the interfaces of classes should be broken into
groups of member functions. Each groups servers a different set of clients.
Separation can be achieved by:
- Separation through Delegation
- Separation through multiple inheritance
If this principle is violated then there is a coupling between all clients
Polyad vs Monad
Monad is when properties are grouped into 1 single object that is then passed
in a function parameter. Unfortunately this brings a dependency across all
properties in that single object. Therefore its better to pass smaller objects
(Polyad), in this way the dependencies are broken into smaller groups.
Interface Pollution
As we build up classes there is a tendency for us to add functionality that
is specific for a particular implementation. In this way the interface gets
populated by functions and properties that are not required if the class was in
a different context, thus making the interface fat. In this way this violates
the Liskov Substitution principle. Separate Clients means separate
interfaces
There is a backward force applied by clients upon interfaces. For example a
user may wish to add a trivial extra function that cannot be exactly positioned
in existing interfaces.