Design patterns did not just appear out of thin air. They
evolved from the application of certain design goals, which are widely known
throughout the O-O community and which are taught along with almost every O-O
language. These include low-coupling, encapsulation and high-coherence. You
are expected to know these already. To this the GoF have added certain
higher-order design principles which help us to understand how the patterns
came about and use them to test our application of the patterns.
Interface inheritance versus Implementation inheritance
The GoF hate inheritance so much that you would think one of
them inherited a dog that bit him. OK, so that is a bad attempt at humor, but
the point is they really abhor class inheritance and with very good reason. The
truth is that any person who grew up in a Ghetto, but rejected the values
generally passed on in those circumstances, would understand why class
inheritance can be a terrible thing. It locks in the behavior that your class
is based on the parent class it inherits from. Inheriting from a super-class
is like getting from your behaviors hard-coded into you through the passing on
of your parents’ genes. It is hard to override inappropriate behaviors.
This, obviously, is why you have Interfaces in VB along with
a compromise between class and interface inheritance: Abstract (MustInherit)
Classes, along with a plethora of ways to inherit methods (mustoverride,
overridable, notoverridable and shadows) in VB. In social terms, Interfaces
give you principles to live by. These help you to figure out how to respond to
life on a situation by situation basis, while parent inheritance means that you
are limited to behaving the way your parents’ genes dictate.
Let us look at everyone’s favourite issue, money. As
responsible adults, we all have to earn a salary which allows us to care for
our families. However, we also need to ensure that the money we have stretches
until more comes in.
We can declare an interface for a fiscally responsible
person:
Interface GoodProvider
Function EarnMoney ()
Function ManageMoneyWisely()
End Interface
Your parents might have implemented it as:
' Parents interpretation of fiscal responsibility
Class YourParent
Implements GoodProvider
Function ToiledInTheField () Implements GoodProvider.EarnMoney
MessageBox.Show("I go to my farm and plant and reap to sell at market")
End Function
Function KeepMoneySafe() Implements GoodProvider.ManageMoneyWisely
MessageBox.Show("I keep my money under the mattress")
End Function
End Class
But times change and so your implementation might be:
' You adapt to the new circumstances while fulfilling the same requirements:
Class ModernPerson
Implements GoodProvider
Function ExcelAtCareer() Implements GoodProvider.EarnMoney
MessageBox.Show("I develop software using design patterns!")
End Function
Function InvestAstutely() Implements GoodProvider.ManageSalaryWisely
MessageBox.Show("I talk to my stock broker regularly")
End Function
End Class
Education generalizes our career options and allows us to
earn a salary in any one of thousands of ways, some of which are more suited to
our tastes, styles, abilities and makeup than others. By specifying the
required interface which we need to implement and allowing the class which
implements the functionality to worry about how to get it done, we encapsulate
that knowledge and allow for loose coupling between your classes and the clients
which use them (in this case your children).
On the other hand, if we were to use class inheritance in
this case, we would be locked into the behaviour of the parent class. It would
mean that if the parent class implemented the WorkedAtTheirJob using farming
techniques, we similarly end up farming. If we found it impossible to keep
farming then we would be in trouble! The problem is that left to the default
situation, the default behaviour is carried through. With interfaces and
abstract classes, there is less default behaviour and so the natural thing is
to figure out how to implement the functionality in each class. It also means
that we can interchange the implementing classes (so a child provided for by a
stock broker can be just as well taken care of as a software developer’s child)
As the GoF point out, Inheritance breaks encapsulation since
the behaviour and functionality of the subclass is tied to the ParentClass.
Object Composition instead of Inheritance
Think of Class inheritance versus Object composition in this
way. Suppose you got your car tires from the manufacturer, with the tire fixed
inextricably to the rim. This would be a problem and you have one solution:
drive extremely slowly so the tread never wears out. The better way is to make
the wheel in a manner which allows it to be composed of a separate tire and
rim. If anything happens to one, you just change it and the other is still
good to go. The rim gets bent, you replace (or straighten) it. If the tire
gets worn, buy a new tire and have them put it on the rim. The sweet thing is
that it allows you to not only fix problems such as tire wear, but also enhance
performance. If you want a quieter or more fuel efficient roll, you can change
the tire material; want better handling, go for lower profile tires. Supposing
you like the way your car looks but think the rims could lift the appearance,
you can setup the car to look vastly different by changing the rims. The cosmetic-object
in this case is the rim and you can extend the look by changing it.
In an organization, object composition is what we do with
departments and teams. Instead of saying "I want someone to tune the
database, manage security permissions for it, implement changes to table
structures," we simply say “Get me a DBA.” Throw in a couple of people
who know how to write code (Programmers), someone who knows some advanced
VB.NET (UML), and knows how to talk to users (Software-Engineers) then you have
a development team. The Bridge pattern, Adapter pattern and Command pattern
and some other patterns rely on this principle of object composition.
Find what varies and encapsulate it
When you look at an automobile, you see that there is disposability
built into the parts. If they did not do it this way, you would have to change
your whole engine when any major part wore out. Cars would experience
incredible plunges in resale value for every 10,000 or so miles they drive. After
50,000 miles most cars would be absolutely un-maintainable. What causes mechanical
engineers to have to build in varying levels of disposability of parts?
Well, there is an engineering principle that the more moving
parts you have in a machine, the more susceptible it is to breakdown. The
moving parts wear against each other and fail. The ordinary person sees this most
in their shoes. Thus, engineers build in changeable bearings, bushings and
rubbers; parts which represent the interfaces between the moving parts and so
bear the brunt of the wear. Being mostly made of rubber, they are relatively
inexpensive to change once the wear out. You see this clearly in good software,
except that instead of parts that literally wear against each other, we have
code that wears against moving and changing user requirements. Just as car makers
build loose coupling into their car parts for interchangeability due to wear,
we must build in interchangeability within our code. We must determine what functionality
requirement can vary over time, encapsulate it and instead of interacting with
it directly, interact with it through an interface. Many times it means making
the structure of our code a bit more involved, but because we do it with
recognizable design patterns, the developer who is grounded in patterns can
almost instantly figure out the interactions within our classes and figure out
which classes to change, substitute or add when a change in requirements comes
about. This is similar to a mechanic who knows which front end part to check
and change when a tire wears unevenly. And just as a mechanic knows which
particular bolts to pull in order to change a front-end part without pulling
the whole front-end, our software developer knows how the new class should be “bolted”
into the other parts in our system. So when a change is made, he can quickly
access the code in the rest of the system which refers to that
changed/substituted class in order to swing the updated or new class into use.
My contribution to the Design Principles: Separate
Object models from Algorithms
The GoF did not mention this as a general design principle
or theme. They did mention it as a benefit of the Visitor pattern. But
looking at Composite, Iterator, Visitor and Interpreter, I see when it is worth
generalizing it into a design principle because it is a recurrent theme running
through those patterns.
The point is you should isolate the algorithms that you use
on an object model from the object model itself as much as possible. This may
not seem clear right now, but when you look at the patterns mentioned above you
will see the power of applying this principle. You will be able to change
object-hierarchies in a way which does not interfere with the operations that
need to work on the components within that model.
Incidentally, some folks will realize that the separation of
object model from algorithms is very similar to the principle of encapsulating
what varies, since in most cases the object model tends to be very stable while
the algorithms will evolve over time.