I came into this field from a very non-technical background. And when I say very non-technical, I mean that I was an elementary fine arts teacher. The most important calculation I performed on a daily basis was counting my kindergarteners when they lined up to leave to make sure I hadn’t lost any since the beginning of the class period.
Because of my very non-technical background, one of the things I have struggled with the most as a developer is overcoming my fear of technical terms. I have learned a lot of concepts about good software design the hard way—by running into problems, and then looking for better ways to solve them than the techniques I’ve traditionally relied on.
But when I started reading more about common design patterns, a funny thing happened. After sifting through technical jargon and 1990’s UML diagrams, I was surprised to find some patterns I’m already using. Knowing their names just makes it easier to communicate the ways in which I am organizing my code. While this might be an extreme simplification for those of you who didn’t spend most of your career counting kindergarteners, here are two of my favorites put into terms I can understand.
The Strategy Pattern
Let's say I want to play the same song on two different instruments.
Note: My examples are in Typescript.
What can I say, I took piano lessons for fifteen years, but haven’t played the clarinet since the sixth grade.
Anyway, what if I wanted to add another instrument? This is where it gets messy. Every time I want to add something new, I would have to go back inside the
makeMusic function to change it. Maybe it’s time to employ a design pattern.
If I use the strategy pattern, I am injecting an algorithm into a function rather than using conditional logic to determine its behavior. Here are a couple of helpful hints about the strategy pattern:
A strongly typed language allows me to inject an instance of an interface into a class or a function. Built-in type-checking behavior will guarantee that every instance of this interface has a
In dynamically typed languages, it is sometimes called duck typing. The idea behind duck typing is that my
makeMusicfunction shouldn't care what kind of object is passed into it as long as it has a
playfunction. In this case, if it looks like an instrument, and sounds like an instrument, then we will say it is an instrument.
This pattern is especially helpful for mocking out behavior for tests. For example, if the
playfunction was loading data from another class or an API, we might not want to load that data every time we run our tests. Instead, we can test the behavior of the class with a mock.
The Decorator Pattern
Chances are, if you have ever worked with Java, you have seen something like this:
If this looks familiar—congratulations! You have already used the decorator pattern. I sometimes like to call this one the “Russian Doll Pattern," because there is an object nested inside an object inside an object.
So, let's say I am playing “Twinkle Twinkle Little Star.” Once I’ve played it a few times, it gets pretty boring and I want to mix it up a bit. I could play it fast or slow, loud or soft, in a different key or style. There are an infinite number of combinations, but how do I account for all of them in a single function?
Let's use the decorator pattern to write some variations on "Twinkle Twinkle Little Star." After all, if Mozart were a programmer, I think this is what he would do.
This is the base behavior for my class. No matter how I extend this behavior, I should never have to go back into the base class to change its core behavior. Now let's compose some variations.
First, we need to create an abstract class.
Rather than going inside a pre-existing class to add functionality, we can use this pattern to make classes extendable.
In addition to making it easier for us to change our own code, it allows anyone else using this library to extend classes without altering source code. To return to my first example, Java's use of decorators in reader functions allow us to perform our own transformations on incoming data while still taking advantage of built-in functionality.
This pattern works best if the function being called takes an argument and returns a value. This is why the song title in this example is an argument to the
playfunction rather than the class that contains it. Getting the final return value of this function is like playing a game of telephone—the output changes each time the function is called.
Whether you are just starting out or are a veteran in the industry, I think we've all had moments where breaking down technical concepts in the right way helps us to understand something that once seemed complicated and scary. In my case, learning more about design patterns had immediate effects on the way I write and talk about code. If you want to learn more, my advice would be to try these patterns out for yourself by choosing a simple coding challenge, and trying to solve it in different ways. The results might surprise you!