Fancy words in Tech Episode III: Design patterns

This is a immense topic, but on this post I will try to summarize the core concepts and explain and the most common design patterns in software (or at least those I've personally run into during my career).

I'll not pretend and say that I've read back to back Design Patterns by the GoF ✌️, this will most likely be a "cheatsheet"-like article with examples and some opinions.

What are Design Patterns?

You can say these patterns are some kind of elegant solutions to repeating problems in software development. In a nutshell a design pattern shows you how you should structure your classes and how these classes should communicate with each other.

My main takeaway from reading about this topic is that once your learn a couple (the most common ones) you will recognize them everywhere, so learning a new framework for example will become much more easy.

It also helps us 'communicate' with other developers and coworkers in a more abstract way; like when working on a new feature... like: "let's implement a singleton pattern for our database connection"... instead of: 'lets have some kind of global variable somewhere to keep the reference'.

Also helps when you hire someone; if your project follows some of these patterns and they know the basic concepts seeing something like:

let connection = ResultsServer.getInstance();

// or ...

let car = CarBuilder()
             .addEngine("v10")
             .addFuel(8.0)
             .build();

// will become insta-recognizable

Not all is great, theoretically sure... but in practice i've ran into many projects that where a complete pile of ... patterns-on-top-of-patterns and the actual business-logic was hard to understand. Same thing with OOP programming... when it becomes a massive tree of class-hierarchy it actually becomes even harder to do any kind of structural modifications.

So be careful of not falling into that, specially when writing something from scratch like a library.

Categories

The book I mentioned before covers about 23 different design patterns but there are many more (and undocumented). One example is the Repository Pattern that became a thing years later after the book was released and currently is present in many ORMs and database-like libraries.

For now I will just focus on the following ones that in my experience are the most used ones. We split them in three categories:

Creational Patterns

Different ways to create objects. Instead of you instantiating all objects very specifically (and explicitly), these patterns give you more flexibility in how the objects are actually created.

Factory Pattern

Example here Github @ factory.ts

  • Decoupling: The client code is decoupled from the creation of concrete objects.
  • Flexibility: New shape types can be added without modifying existing code.
  • Encapsulation: The creation logic is encapsulated within the factory.
  • Reusability: The factory can be reused in different parts of the application.

Builder Pattern

It separates the construction of a complex object from its representation. It allows you to build objects step-by-step, providing flexibility and control over the creation process.

Example here Github @ builder.ts

  • Separation of Concerns: Separates the construction of a complex object from its representation.
  • Flexibility: Allows for creating different variations of the same object.
  • Control: Provides control over the construction process.
  • Readability: The builder pattern often results in more readable code.

Singleton Pattern

Ensures that a class has only one instance and provides a global point of access to it. It's often used for objects that need to be shared across the application, like a database connection or a logger.

Example here Github @ singleton.ts

Key Points:

  • Thread Safety: In multi-threaded environments, consider using synchronization mechanisms to ensure thread safety.
  • Dependency Injection: While Singleton can be useful, overuse can lead to tightly coupled code. Consider using dependency injection to manage dependencies more flexibly.
  • Alternatives: For less global concerns, consider using module patterns or dependency injection frameworks.

When to Use Singleton:

  • Global Configuration: For application-wide settings.
  • Shared Resources: For objects that need to be accessed from multiple parts of the application.
  • Controlling Instantiation: To ensure only one instance of a class exists.

Caution:

  • Overuse: Overusing Singleton can lead to tightly coupled code and make it difficult to test.
  • Global State: Be mindful of global state and side effects.

Structural Patterns

These help you with how inheritance and composition(and aggregation) can be used to provide extra functionality. Like the relationship between these objects.

Adapter Pattern

Allows incompatible interfaces to work together. It provides a wrapper class that converts the interface of one class into an interface expected by clients.

Example here Github @ adapter.ts

Benefits of the Adapter Pattern

  • Reusability: Allows you to reuse existing code that has incompatible interfaces.
  • Flexibility: Provides a way to adapt to changing requirements or interfaces.
  • Maintainability: Provides a way to adapt to changing requirements or interfaces.

Use Cases

  • Integrating legacy systems: Adapting old systems to work with new ones.
  • Third-party libraries: Adapting third-party libraries to fit your application's requirements.
  • Different platforms: Adapting third-party libraries to fit your application's requirements.

Facade Pattern

Provides a unified interface to a set of interfaces in a subsystem. It simplifies the interaction with the subsystem, making it easier to use.

Example here Github @ facade.ts

  • Simplified interface: Provides a higher-level interface, making the subsystem easier to use.
  • Loose coupling: Decouples clients from the underlying subsystems.
  • Abstraction: Hides the complexity of the subsystem.

Use Cases:

  • Complex subsystems: Simplifying interactions with complex subsystems.
  • Layered architectures: Providing a facade for a layer of the architecture.
  • Legacy systems: Modernizing legacy systems by providing a more user-friendly interface.

Behavioural Patterns

Interaction or communication and assignment of responsibilities between our objects

Observer Pattern

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Example here Github @ observer.ts

  • Decoupling: Observers are decoupled from the subject, making the code more flexible and maintainable.
  • Scalability: You can easily add or remove observers without affecting the subject's code.
  • Efficiency: Observers are only notified when necessary, reducing unnecessary updates.

Use Cases:

  • Event-driven systems: For example, GUI frameworks, real-time applications, and game engines.
  • Model-View-Controller (MVC) architecture: For updating views when the model changes.
  • Data synchronization: For keeping multiple components in sync.

Iterator Pattern

It separates the traversal logic from the collection itself, promoting flexibility and encapsulation.

Example here Github @ iterator.ts

  • Encapsulation: Hides the internal structure of the collection.
  • Flexibility: Allows for different traversal strategies without modifying the collection itself.
  • Iterability: Makes collections compatible with built-in iteration constructs like for...of and spread operator.

Strategy Pattern

Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

Example here Github @ strategy.ts

  • Flexibility: The Strategy pattern allows you to change the algorithm used at runtime, providing flexibility and adaptability to your application.
  • Encapsulation: By encapsulating algorithms in separate classes, you promote code reusability and maintainability.
  • Extensibility: New algorithms can be added without modifying existing code, making it easy to extend the functionality of your application.
About
Related Posts
  • Fancy words in Tech Episode II: GraphQL

    It's been around for many years, and like other buzz-words many companies choose the tech just because "certain FANG company uses", then It must be good for my solution. Here i will try to summarize the most important key points of this technology, when and where is intended to be used and what a developer does when working on a project like this.

  • Using Makefiles to improve Docker image build experience

    Makefile is a building tool that's been around forever; and while some of us may remember it from building our first couple projects (and may hate those times and the struggle); But I believe it's an awesome tool that by just adding a single file to our project It can speed / easy up our daily work flow on: how we build, run locally, etc our 'Dockerized' projects

  • The SOLID Programming Principles OOP(s)!

    We can think of these as a general guide. There are many more, here are the ones I come up often, they are five and go by the acronym of S.O.L.I.D. The good thing about SOLID is that helps you decouple/breaking apart your code. Giving our modules independence of each other and make our code more maintainable, scalable, reusable and testable.

Comments
The comments are closed on this section.
Hire me!

I'm currently open for new projects or join pretty much any project i may fit in, let me know!

Read about what I do, or contact me for details.