Two Design Patterns You're Probably Already Using

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.

 1 let instrument: string;
 2 
 3 function makeMusic(): void {
 4     if (instrument === "piano") {
 5         console.log("Now playing: something by Mozart");i
 6     } else if (instrument === "clarinet") {
 7         console.log("Squeeeeak!!");
 8     }
 9 }
10 
11 instrument = "piano";
12 makeMusic();
13 // => Now playing: something by Mozart

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.

 1 interface InstrumentStrategy {
 2     play();
 3 }
 4 
 5 class Drum implements InstrumentStrategy {
 6     play() {
 7         console.log("Thump.");
 8     }
 9 }
10 
11 function makeMusic(instrument: InstrumentStrategy) {
12     instrument.play();
13 }
14 
15 makeMusic(new Drum());
16 //=> Thump.

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:

  1. 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 play function.

  2. In dynamically typed languages, it is sometimes called duck typing. The idea behind duck typing is that my makeMusic function shouldn't care what kind of object is passed into it as long as it has a play function. In this case, if it looks like an instrument, and sounds like an instrument, then we will say it is an instrument.

  3. This pattern is especially helpful for mocking out behavior for tests. For example, if the play function 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.

1 it("plays a song", () => {
2     const instrument = new MockInstrument();
3     spyOn(instrument, "play");
4 
5     makeMusic(instrument);
6 
7     expect(instrument.play).toHaveBeenCalled();
8 });

The Decorator Pattern

Chances are, if you have ever worked with Java, you have seen something like this:

BufferedReader fileReader = new BufferedReader(
    new FileReader(
        new File("some.file")));

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.

 1 interface Song {
 2     play(title: string): void;
 3 }
 4 
 5 class Theme implements Song {
 6     play(title: string): void {
 7         console.log(`Now playing "${title}"`);
 8     }
 9 }
10 
11 const song = new Theme();
12 song.play("Twinkle Twinkle Little Star");
13 //=> Now playing "Twinkle Twinkle Little Star");

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.

 1 class Variation implements Song {
 2     constructor(protected song: Theme) { }
 3 
 4     play(title: string): void {
 5         this.song.play(title);
 6     }
 7 }
 8 
 9 class Minor extends Variation {
10     play(title: string): void {
11         title = `a minor variation on ${title}`;
12         this.song.play(title);
13     }
14 }
15 
16 class Classical extends Variation {
17     play(title): void {
18       title = `a classical variation on ${title}`;
19       this.song.play(title);
20     }
21 }
22 
23 const variation = new Classical(new Minor(new Theme()));
24 variation.play("Twinkle Twinkle Little Star");
25 //=> Now playing "a minor variation on a classical variation of Twinkle Twinkle Little Star"

Rather than going inside a pre-existing class to add functionality, we can use this pattern to make classes extendable.

  1. 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.

  2. 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 play function 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.

Final thoughts

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!

Becca Nelson, Software Crafter

Becca Nelson is a Software Crafter in Chicago who is interested in development, design, and education.

Interested in 8th Light's services? Let's talk.

Contact Us