5 TypeScript Design Patterns You Should Know

Fernando Doglio
Bits and Pieces
Published in
8 min readAug 17, 2020

--

Design patterns are great problem solving templates that developers can apply to their projects. There are way too many patterns to cover in a single article though and they tend to attack different needs. However, they can losely be categorized into three different groups, you have:

  • Your structural patterns, they deal with structuring the relationship between different components (or classes) and forming new structures in order to provide new functionalities. Examples of structural patterns are Composite, Adapter and Decorator.
  • Your behavioral patterns, they help abstract common behavior between components into a separate entity which in turn, and your creational patterns. Examples of behavioral patterns are Command, Strategy, and one of my personal favorites: the Observer pattern.
  • Your creational patterns, they focus on class instantiation and making your life easier in order to create new entities. I’m talking about Factory method, Singleton and Abstract Factory.

And although they can be implemented directly in JavaScript, specially now with ES6, the OOP approach that TypeScript takes makes it very simple and straightforward to follow generic guides (or even from other OOP languages) and gain all the benefits of these patterns (as opposed to having to work around some of the limitations vanilla JS has in regards to OOP).

Singleton

The singleton pattern is probably one of the most known design patterns out there. It is a creational pattern because it ensures that no matter how many times you try to instantiate a class, you’ll only have one instance available.

This is a great pattern to handle things such as database connections, since you’ll probably want to only handle one at a time, instead of having to re-connect on every user request.

Now, granted, you can’t directly instantiate the class, but with the getInstance method, you can be sure you won’t have more than one instance. In the above example, you can see how a fake class that would wrap the database connection would benefit from this pattern. Ths id property could easily be thought of as thee actual connection, and this little test is showing you how that “connection” is always going to be the same one, no matter how many times you call the getInstance method.

The output from this code is ofcourse:

0.4047087250990713
0.4047087250990713
0.4047087250990713
0.4047087250990713
0.4047087250990713

Tip: Use Bit (Github) to share, document, and manage reusable components from different projects. It’s a great way to increase code reuse, speed up development, and build apps that scale.

Bit supports Node, TypeScript, React, Angular, Vue, and more.

Angular components shared on Bit.dev

Factory method

As I’ve already mentioned, the Factory Method pattern is a creational pattern, just like Singleton. However, instead of directly working on top of the object we care about, this pattern only takes care of managing its creation.

Let me explain: imagine you have to write code that will move vehicles, they are very different types of vehicles (i.e a car, a bicycle and a plane), the movement code should be encapsulated inside each vehicle class, but the code that calls their move method can be generic.

The question here is how are you going to handle the object creation? You could have a single creator class with 3 methods, or one method that receives a parameter. In either case, extending this logic in order to support the creationg of more vehices requires you to keep growing the same class.

However, if you decided to use the factory method pattern, you could do something like below:

So now, the code required to create a new object is encapsulated into a new class, one for each vehicle type. This ensures that if you need to add vehicles in the future you just need to add a new class, without having to modify anything already existing.

Let’s take a closer look at how we would implement this with TypeScript:

That’s a lot of code, I know, but you can follow it using the diagram above. Essentially in the end, we care about the custom handlers, and I called them handlers instead of creators (which is how other literature might call them) because they don’t only take care of creating the objects, but they also have the logic that uses them (as you can see by the moveVehicle method).

The beauty of this pattern, is that if you wanted to add a new vehicle type, all you have to do is add its vehicle class and its handler class, without growing the LOC of any other class.

Observer

Out of all patterns, my favorite is the Observer, and that is because of the type of behavior we can achieve with it. Heard about ReactJS? Based on it. What about event handlers in front-end JavaScript? Based on it. At least based on the theory, I honestly don’t know how each browser implements their event system, but it’s a good enough guess.

The point is, with the Observer pattern you can implement them and more.

And how does it work? Essentially the pattern states that you have a set of observer objects, which will react to changes in the state of the observed entity. In order for this to happen, once a change is received at the observed end, it is responsible for notifying its observers by calling one of its methods.

In practice, the implementation of this pattern is relatively easy, let’s take a quick look at the code and then review it:

As you can see, with two abstract classes we can define the Observer which is going to represent the objects that react to changes on the Observable entity. In our example above, we’re pretending to have an InputElement entity (similar to how you have your HTML input fields on the front-end) that gets clicked, and one ConsoleLogger that logs everything that happens to the console, without us having to do anything.

The beauty of this pattern is that it allows us to know and react to the internal state of the Observable without having to mess with its internal code. We can keep adding Observers that do other things, even some that react to specific events, and then have their code decide what to do on each notification.

Decorator

The decorator pattern attemps to add behavior to an already existing object during run-time. In a way, you can think of this as dynamic inheritance because even though you’re not creating a new class in order to add the behavior, you’re creating a new object with the extended functionality.

Think about it this way: you have your Dog class, with a move method aand now you want to extend its behavior because you want a super dog, which can fly when you tell it to move, and a swimming dog, which goes into the water when you tell it to move.

Normally, you would add the standard moving behavior inside the Dog class and then extend that class in two ways, a SuperDog and a SwimmingDog class. However, if you wanted to mix those two then you’d had to then again, create a new class extending their behavior. However, there is a better way.

Composition allows you to encapsulate custom behavior inside different classes and then use the pattern to create new instances of those classes by passinng the originaal object to their constructor. Let’s take a look at the code:

Let’s pay attention at a few details:

  • The SuperDecorator class is indeed, extending the Animal class, the same class that the Dog class extends. This is because the decorator needs to provide the same public interface that the class its trying to decorate.
  • The SuperDecorator class is abstract which means you don’t really work with it, you just use it to define the constructor which will keep the copy of the original object in a protected property. The overwrite of the public interface is done inside the custom decorators.
  • SuperAnimal and SwimmingAnimal are the actual decorators, and they are the ones that add the extra behavior.

The benefit of having this setup, is that if you wanted to mix both behaviors into one, thanks to the fact that all decorators are indirectly extending the Animal class as well, you can do the following:

console.log("--- Now let's go SUPER swimming --- ")const superSwimmingDog =  new SwimmingAnimal(superDog)superSwimmingDog.move()

A much more dynamic result that if you were to be using classical inheritance.

Composite

For the last pattern to be covered here, I wanted to talk about the Composite pattern, a very useful and interesting pattern when it comes to handling multiple similar objects together.

In other words, this pattern allows you to handle a set of similar components as a group, being able to execute a particular operation on them and aggregating the result from them all.

The interesting part about this pattern though, is that it’s not a simple group of object, it can contain entities or groups of entities, and each group can at the same time, contain more groups. This is what we would call a Tree, you can read more about trees and other data structures over here.

Let’s take a look at an example:

In the above example, we have products that can be put into boxes, and boxes that can also, be put inside other boxes. This is a classic example of a composite, because what you’re trying to achieve is to get the complete price of the delivery you’re making, thus you want to add the price of every element inside the big box (including the price of each individual smaller box).

Thus, the normally called “component” element, is the Product class, otherwise known as the “leaf” element inside the tree. This is because this entity has no children. While the Box class is the composite itself, having a list of children, all of them implementing the same interface. That last part is because you want to be able to iterate over all children and execute the same method (remember that here a child can be another, smaller composite).

The output from the example should be:

Adding a  Bubble gum to the box
Adding a Samsung Note 20 to the box
Adding a Samsung TV 20in to the box
Adding a Samsung TV 50in to the box
Adding a A box with 2 products to the box
Total price: 2105.5

So, consider using this pattern when it comes to dealing with multiple objects that follow the same interface. You’ll find it helps in simplifying the way you interact with the group, by hiding that complexity inside a single entity (the composite itself).

Conclusion

Design patterns are perfect tools to use for problem solving, although no matter how many examples you see online, none will fit your needs exactly, you’ll have to understand them and modify them a little to make them work. Either that or modify your business logic to make the pattern fit, either way, it’s always a good investment.

What about you? Which one is your favorite pattern? Are you able to use them in your day-to-day projects or have you only read about them but are still wondering if they’re actually useful?

Leave a comment down below, I’d love to know!

Until then, see you on the next one!

Related Stories

--

--

I write about technology, freelancing and more. Check out my FREE newsletter if you’re into Software Development: https://fernandodoglio.substack.com/