Programming with Patterns

Nitish Sharma
The Startup
Published in
7 min readOct 24, 2019

--

Don’t keep reinventing the wheel.

If a particular problem keeps reappearing, instead of an on the spot solution, we will come up with a standard operating procedure to cover it. This is where design patterns come into the picture.

Before diving into these design patterns let’s take a look at the their origin.

Gang of Four

Wheel reinvention is a constant problem for software engineers. It is not that we like repeating things but just that we subconsciously end up being redundant.

Sometimes when we are designing applications it is hard to realise that the circular friction reduction device with a central axis that we have just built is, in fact, a wheel.

Software design can pretty complicated that it is not very surprising to miss the patterns of challenges and the ever repeating solutions.

In 1995, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides published Design Patterns: Elements of Reusable Object-Oriented Software. The book became an eye-opening hit and made the authors famously known as the Gang of Four (GoF).

The GoF did two things for us:

  1. They brought design patterns into our software engineering lives, where each pattern is a prepackaged solution to a common design problem.
  2. They identified, named, and described 23 recurring patterns, that they saw as key to building clean, well designed object-oriented programs.

Patterns for Patterns

These ideas given by GoF basically mean 4 things:

• Separate out the things that change from those that stay the same.
• Program to an interface, not an implementation.
• Prefer composition over inheritance.
• Delegate, delegate, delegate.

And a wildcard entry:

• YAGNI

Let’s look at them in detail

Separate Out the Things That Change from Those That Stay the Same

Life would be a lot easier if things didn’t change. Well, same goes for software engineering.

Change is the only constant

Ideally, all changes are local. We should never have to go through all of the code because A changed, which forced a change in B, which led to a change in C, and like that rippled all the way down to Z.

One way to achieve an ideal system is by separating the things that are likely to change from the things that are likely to stay the same.

If we can identify which aspects of our design are likely to change, we can isolate those parts from the more stable parts. When requirements change or a bug fix comes along, we will still have to modify our code but hopefully with this approach the changes can be confined to those change prone areas and the rest of our code can stay unaffected.

Program to an Interface, Not an Implementation

A good start would be to write code that is less tightly coupled to itself in the first place.

eagle = Eagle.new 
eagle.fly(100)

Here we create an instance of Eagle and call fly method on it. This will work only as long as we are dealing with only one kind of bird. We will have a problem the moment a different kind of bird, Ostrich, comes into the picture.

# Dealing with eagles and ostrichesif is_eagle
eagle = Eagle.new
eagle.fly(100)
else
ostrich = Ostrich.new
ostrich.run(100)
end

The above code solves our problem but in a very messy way. Our code is now coupled to both Eagle and Ostrich, and the moment a Duck which can swim comes along it will only get messier.

A better solution would be to use polymorphism and implement a common interface which can be used by all kinds of birds.

bird = get_bird
bird.travel(100)

The above code implements the principle of programming to an interface.

The idea here is to program to the most general type we can i.e not to call an eagle an eagle if we can get away with calling it a bird.

Prefer Composition over Inheritance

Inheritance most of the time seems like the solution to every problem. With inheritance we could get implementation for free, just subclass the parent and we can take advantage of all the good stuff in the parent class straight away.

But there are some strings attached. When we subclass a class, we are not really creating two separate entities but making two classes that are bound together by a common implementation.

Inheritance, by nature, tends to marry the subclass to the superclass.

Any change the behaviour of the superclass can, with a very good possibility, change the behaviour of the subclass.

If our goal is to build applications that are not tightly coupled together, where one change does not ripple through the code, breaking stuff as it goes, then maybe we should not rely on inheritance as much as we do.

We can assemble the behaviours we need through composition.

As opposed to inheritance, we can assemble functionality from the bottom up. To do so, we look for objects which a need a particular functionality and provide them with an object that supplies that functionality.

We try to avoid saying that an object is a kind of something and instead say that it has something.

class Phone
# Phone related code

def focus_camera
# focus on the object in view
end
def capture_image
# capture the image in view
end
end
class SmartPhone < Phone
# SmartPhone related code
def click_picture
focus_camera
capture_image
end
end

Our phone needs to click pictures, but so will a lot of other phones, so let’s abstract out the camera code and put it up in the common Phone base class.

That’s great, but now all phones are required to have a camera. We will again find ourselves in a mess if we come across a LandlinePhone or a CordlessPhone. Also, the details of the camera are exposed to the SmartPhone class, after all, the camera is being managed by Phone and a SmartPhone is nothing but a kind of Phone.

We need to separate out the changeable from the static, by putting the camera code into a stand-alone class of its own:

class Camera
# Camera related code
def focus
# focus on the object in view
end
def capture
# capture the image in view
end
end
class Phone
# Phone related code
end
class SmartPhone < Phone
def initialize
@camera = Camera.new
end
# SmartPhone related code def click_picture
@camera.focus
@camera.capture
end
end

Assembling functionality with composition offers a lot of advantages:

  • The camera code is now a stand-alone reusable class
  • Abstracting out the camera code from Phone allows us to have a simplified the Phone class.
  • We have also increased encapsulation. Separating out the camera code from Phone ensures that an interface exists between the smartphone and its camera. As opposed to having all details of the camera implementation exposed to all the methods in Phone, we now make the smartphone use the interface of the Camera class to do anything to its camera.
  • We can also have other kinds of cameras. So now our smartphone is not stuck with one camera implementation for its the entirety of its life.

Delegate, Delegate, Delegate

Delegation is just a simple “pass the buck” technique

class SmartPhone < Phone
def initialize
@camera = Camera.new
end
# SmartPhone related code def click_picture
camera_focus
camera_capture
end
def camera_focus
@camera.focus
end
def camera_capture
@camera.capture
end
end

Someone calls the camera_focus method on our SmartPhone. The smartphone object says, “Not my department” and passes the buck to the camera.

Composition and delegation if used together are a very solid alternative to inheritance. We get most of the advantages of inheritance, a lot more flexibility, and no unpleasant side effects. Of course, this adds an extra method call, but in most cases its effect on the performance is negligible.

YAGNI (You Ain’t Gonna Need It)

The YAGNI principle says simply that we should not implement features, or design in flexibility, that we don’t need right now, because chances are, we ain’t gonna need it later, either.

A well-designed system can gracefully handle of bugs, ever changing requirements, framework changes/updates, and design changes.

The YAGNI principle calls for focussing on the things that we need right now and working only on the flexibility that we know we need.

If we are not sure that we need something right now, don’t build it till we do. Instead, direct those efforts into building the things that we definitely need right now.

At the heart of the YAGNI idea is the simple realisation that we tend to be wrong when we try to anticipate exactly what we will need in the future.

When we change or add something in our application before we really need it, we make a two-fold bet:

  1. We bet we will definitely need this feature. Most of the time predictions are hard, especially about the future.
    If it turns out that we never need to work this feature which potential use in the future then all the time and effort put into building it is essentially wasted.
  2. When we build something before we actually need it is even more risky. In doing so we are also assuming that we can get it right, that we know how to correctly solve a problem that we are not really facing yet.

We should instead put that time and energy into getting a better understanding of what we need right now and how to go about building it.

The proper use of design patterns is the art of making our system just flexible enough to deal with the problems we have today, but no more.

Patterns are not stand alone solutions, but just useful techniques. They can help us build a functioning application, but it will not work better because we applied all combinations of 23 of the GoF design patterns to build it. Our code will work better only if it focuses on the job it needs to do right now.

--

--