Robert Martin, affectionately called Uncle Bob, in many ways has been one of the towering figures in Object Oriented development methodology! He is the pioneer of the clean-code movement and has laid out some fundamental principles in object-oriented development methodology. I believe it is possible to describe most of GoF patterns in terms of the first principles proposed by Uncle Bob. These principles are immortalized by term SOLID principles.
In the
remaining article we will try to study the rationale behind few GoF patterns
and examine whether the SOLID principles can explain the motivations behind
each GOF pattern by first principle thinking.
Once each
pattern is broken down into set of first principles, it is left to designer’s
flair and “syntactic sugar borrowed” from the programming language construct to
implement or use that pattern.
Let’s revisit each of SOLID principle and connect it
with the GoF Patterns!
SOLID acronym expands into
- Single responsibility
Principle
- Open-Closed Principle
- Liskov substitution
Principle
- Interface Segregation
Principle
- Dependency Inversion
Principle
Single Responsibility Principle (SRP).
A class must
bring only related and cohesive concepts together. It is also emphasized that
“a class must have only one reason to change”. The changes might occur because
the concept that the class wrapped in its definition has undergone a change or
has evolved.
A direct
consequence of following this principle is that this makes software maintenance
and evolution easy. If a change percolates due to maintenance or software
evolution, the changes will be localized in the class boundary that undergoes the
change. Another benefit is the Increase of reuse of the classes. This follows
as the class represents one single concept or responsibility.
"High
cohesion within a module and low coupling between modules", a practice put
forward in structured programming is just an extension of this principle. This
principle paves way for more maintainable software with ability to evolve
easily.
GOF pattern application
Factories
abstract away the object creation responsibility from the class. This helps the
served class (class whose objects are constructed by factory) to concentrate on
the real- world concept it seeks to model. Builder and composite lend similar
help for specific types of complex objects. As a matter of fact, all creational
patterns abstract away the creation part of object from the served class.
Similarly, Structural patterns take
away the responsibility of representing the relation of classes (entities)
among themselves. This helps the classes modelling the real entities classes to
concentrate on representing the concepts they represent. For example, an
Adapter takes abstracts away the difference in interface of two closely related
classes. Iterators take away the traversal from collection and localize the
traversal code. Mediators simplify the communication between objects and take
full responsibility for brokering the communication. Memento takes away the
state representation from a complex object. The Adapters, Iterators, Mediators
and Memento handle single Responsibility!
Taking away one
concern (such as logging, concurrency, traversal) and representing it in one full-fledged
class to simplify the representation of another (entity) class better is
underlying idea for several of GoF patterns. This helps the served class to
narrowly focus on its core responsibility. If we were to extend the Single
Responsibility principle to cover “separation of concerns” then we have a more
emphatic way to cove several other GoF patterns
Open-Closed Principle
A module will be said to be OPEN if it is available for extension. It
should be possible to extend the module with ease. A module will be said to be
closed if available for use by other modules. The principle for module is
extended further for defining boundaries of class.
The basic underpinning thought is that classes should
allow extensions, but it should not necessitate modifications to use it. Put simply,
the interface for classes should be such defined that it allows adding of new
functionality by extending (subclassing) the interface. At the same time, the
interfaces should be stable enough so that modification of existing code is not
needed the class code is reused.
In object-oriented paradigm, adding code to enhance
the functionality by extending the interface is welcomed. However, if code
reuse necessitates adding too many branches and changing existing code, then
the software design is deemed brittle. This principle makes perfect sense
as changing existing code increases demand for tests and make the software
fragile. Adding code by extending interfaces, however has
less ripple down impact and places less demand for re-testing the delivered
software.
Software maintenance phase is true test of how well
the implemented software has adhered to the Open-closed Principle! Open-closed
principle enhances re-use by localising of changes
GoF Patterns
Application
Bridge
pattern: An interface in an inheritance hierarchy can be published, and the
implementation is done its own inheritance hierarchy. This helps the library
provider change implementation without impacting client code, as long as they
have stable interface. The design of extensible interfaces as proposed by
Open-closed principle clearly serves as a guidance in implementing this pattern.
Visitor: The
main problem tackled by visitors is that there is a resistance to change
existing class hierarchy of working code. However, there is a need to
add functionality to all the related objects. Visitor pattern
comes to rescue by introducing another visitor interface that helps
us add functionality without modifying the existing working code but by
adding functionality in visitor classes.
Iterator: Developers are reluctant to adapt code in each container
for specific traversal mechanism. So, a full-fledged class that exposes
container’s traversal mechanism called Iterators is created. This
makes container’s interface more stable and extensions for different traversals
techniques can be added in Iterators.
Liskov substitution
principle
This principle emphasis
careful subclassing and judicious use of inheritance. The child class inherits
all the public and protected interface of the base class, so its concepts must
conform to the base class characteristics.
Inheritance
defines a very rigid relationship between derived and base classes (interfaces).
Changes in Base classes often trickle down to the derived classes. It extends
the Open/Closed Principle by focusing on the behaviour of a superclass and its
subtypes. This principle together with Open-closed principle serves to provide
blueprint for defining cleaner interface and class hierarchy.
GoF Patterns
Application
Abstract
factory: While defining the class of related products, extra care is taken to
extend the Factories of related objects. One abstract interface is exposed to
client and the Concrete Factories implement the interface.
Interface Segregation
Principle
This principle
suggests that when we are faced with a complex interface then it is best
advised to break it into distinct simpler, single concept representing
interfaces. Doing this will save the designers of concrete classes from
inserting dummy implementation in the concrete classes if the concrete classes
do not have really need to implement that method
GoF Patterns
Application
Segregating
interface into smaller neat interface avoids bloated interfaces. Much like
Single Responsibility Principle applied to designing classes the Interface
segregating principle serves as a guideline to design lean interfaces.
Dependency Inversion
principle
High level
modules should not depend on low level modules but instead both should depend
on higher level abstractions. Alternately, abstractions should not depend upon
details but conversely details must depend upon abstractions. This aims to
reduce the coupling between the classes by introducing abstraction between the layer,
thus doesn’t care about the real implementation.
The idea is higher level abstraction are more stable than the lower
level implementation details. The lower level implantation has to absorb much
more variability. If the dependencies are on the implementation details, then very
likely the software will be fragile.
Client code should write code against the interface of the classes rather
than against the concrete classes. This will reduce the changes in client code should
a change in the implementation of classes that provides the API changes
GoF Patterns
Application
Bridge (pImpl idiom)
The client is provided a higher-level interface class which provides stability
to the client code. The implementation hierarchy separately absorbs all changes
in implementation. It’s a classic case of Dependency inversion.
Factory method
and abstract factory also recommends that client code should depend on the
Factories for object creation. The factories are higher-level interfaces, defined
just for object creation. This directly confirms to Dependency Inversion
principle. Similarly, in prototype pattern the client code again depends on the
interface class that exposes “clone” method rather than on concrete classes.