GoF Design Patterns – Overview

Introduction

GoF (Gang of Four) Design Patterns are a collection of 23 design patterns introduced in the book “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. These patterns provide solutions for common problems that occur in software development. They are divided into three groups: Creational, Structural, and Behavioral. Although the original book provides examples in C++, GoF Design Patterns are applicable to any language that supports Object-Oriented Programming (OOP) paradigm.

In this series of articles, I want to provide straight to a point, distilled explanation of GoF Design Patterns, including examples and my point of view on this topic.

How to learn design patterns

Learning the design patterns involves some back-and-forth process. Pattern introduction usually starts with some sort of an overview or a definition that tries to capture general information and problem that patterns solve. Challenge is that usually, this definition is hard to understand when you do not know the pattern, yet it makes perfect sense after you understand the pattern. Because of that, I recommend learning design patterns in the following way:

  1. Read the overview section.
  2. Read the use cases section.
  3. Read the general structure and UML diagram.
  4. Read / understand / practice on example code.
  5. Revisit the overview section.
  6. Revisit the use cases section.
  7. Revisit the general structure and UML diagram.
  8. Implement your own example.
  9. Practice during your daily job (taking into account information from Criticism of patterns, Not everything has to be a pattern, and Hype and patterns adoption from this article)

Definition of a pattern

A pattern is a reusable solution to a problem frequently occurring in a particular context. Patterns are not specific solutions; instead, they provide guidelines for solving problems that can be adapted to different situations. A pattern describes a problem, its solution, and the context in which it applies.

Benefits of patterns

The use of design patterns in software development has many benefits. Patterns provide a proven solution to common problems, reducing the risk of errors and increasing the software’s reliability. They also give the developers a common vocabulary, improving communication and reducing the time needed to share ideas. Patterns also improve the maintainability of software by making it easier to understand and modify.

Criticism of patterns

Despite the many benefits of design patterns, they are not without criticism. One criticism is that patterns can be overused, leading to complex and overly abstract code. Another complaint is that patterns can be misused, making code harder to understand and maintain.

Not everything has to be a pattern

When writing business software, always have the main goal in mind of writing business logic in a simple, clear, easy-to-understand, and maintainable manner. Design Patterns should be used if they fall naturally into the use case that you currently implement. Once you learn all design patterns, you should not force them. Instead, you should keep them as a tool ready to be utilized whenever you struggle with presenting a business problem, as a code in an easy-to-understand way. Design Patterns should inspire you to structure your code to present behaviors clearly. If at any moment you see that your code is getting over complex because of Design Pattern, you should verify if the correct pattern was chosen for the problem that you are trying to solve. In some cases relying only on programming language capabilities might be good enough.

Relation to other principles

Design Patterns are important, and they can improve the way that you write the code. However, it is important to take into account that the following principles take prejudice over design patterns:

  • Clean Code
  • KISS – Keep it simple
  • DRY – Do not repeat yourself
  • YAGNI – You aren’t gonna need it
  • Design for high cohesion, low coupling
  • SOLID
    • SRP – Single Responsibility Principle
    • OCP – Open-Close Principle
    • LSP – Liskov Substitiude Principle
    • ISP – Interface Segregation Principle
    • DIP – Dependency Inversion Principle
  • Principles of Component Cohesion
    • CCP – Common Closure Principle
    • CRP – Common Reuse Principle
    • REP – Reuse/Release Equivalence Principle
  • Principles of Component Coupling
    • ADP – Acyclic Dependencies Principle
    • SDP – Stable-Dependency Principle
    • SAP – Stable-Abstractions Principle

Covering all of the above principles is beyond the scope of articles in this series. However, I am mentioning the above because, in some cases, the above rules allow you to identify that misused pattern. For example, if you apply some pattern and observe code duplicates because of the used pattern, this clearly indicates that the pattern is misused.

Hype and patterns adoption

Like any concept or technology, I believe the design patterns also follow Gartner hype cycle:

Usually, I see the following phases when learning and adopting the design patterns:

  1. Problem phase – Code is hard to read and understand, code is under-engineered.
  2. Solution discovery phase – GoF Design Patterns are discovered.
  3. Hype phase – Code is over-engineered. Everything becomes a pattern. We no longer have a simple constructor, instead factory or builder is used. We no longer have a simple loop, instead visitor, iterator or chain of responsibility is used.
  4. Doubt phase – GoF Design Patterns made the code even worse.
  5. Enlightenment phase – let’s try to figure out why the design pattern did not help. Let’s identify when those should and should not be used.
  6. Proper solution and adoption phase – GoF Design Patterns are used when they improve code structure and readability, in other cases, normal programming language constructs are used.

Design Patterns Types

GoF Design Patterns are divided into three groups:

  1. Creational Design Patterns.
  2. Structural Design Patterns.
  3. Behavioral Design Patterns.

Vince Huston created an excellent graphic that captures all Design Patterns divided into groups:

Creational Design Patterns

Creational design patterns are patterns that deal with the creation of objects. These patterns provide ways to create objects in a manner that is flexible, efficient, and easy to understand.

  1. Factory Method Pattern: allows you to customize a class behavior by overriding the method used to create an object with which the class will collaborate.
  2. Abstract Factory Pattern: allows for the creation of families of related objects.
  3. Builder Pattern: allows creating complex objects using the same construction process and provides the flexibility of choosing attribute values with which object should be created.
  4. Singleton Pattern: ensures that a class is created only once and provides global access to the instance.
  5. Prototype Pattern: allows cloning of existing objects, providing a way to create new objects with the same attributes as existing ones.

Structural Design Patterns

Structural design patterns are patterns that deal with the composition of classes and objects.

  1. Facade Pattern: provides a simplified interface to a complex subsystem consisting of a set of classes.
  2. Composite Pattern: allows to work with individual objects and a group of objects in the same way.
  3. Decorator Pattern: attaches extra responsibilities to an object dynamically, providing a flexible alternative to subclassing for extending functionality.
  4. Adapter Pattern: allows classes with incompatible interfaces to work together by converting the interface of a class into another interface that clients expect.
  5. Proxy Pattern: wraps original object, allowing for another object to intercept calls to original object.
  6. Bridge Pattern: solves the issue of exponential growth of classes that may occur when decomposing the problem being solved into objects.
  7. Flyweight Pattern: memory consumption optimization technique, reduces memory usage by sharing the common and immutable state across many objects.

Behavioral Design Patterns

Behavioral design patterns are all about the communication between objects. These patterns provide ways to manage the interactions between objects in a manner that is flexible, and efficient.

  1. Strategy Pattern: encapsulates a family of algorithms, each one of them is made interchangeable within a particular context.
  2. Template Method Pattern: defines the skeleton of an algorithm in a base class, while some detailed steps are defined in subclasses.
  3. Chain of Responsibility Pattern: links multiple objects into the processing pipeline, which is traversed to handle the request.
  4. Visitor Pattern: allows separating algorithms from the objects on which they operate, groups similar operations that should be applied to different objects.
  5. Observer Pattern: defines a subscription mechanism between objects, allows to observe object state, when object state changes, multiple dependencies are notified.
  6. Command Pattern: encapsulates the entire request as a stand-alone object.
  7. Interpreter Pattern: provides a way to define a language, its grammar, and syntax in an object-oriented way.
  8. Iterator Pattern: provides a way to access the collection of elements sequentially without exposing underlying representation details.
  9. Mediator Pattern: reduces the amount of dependencies between objects, by introducing a mediator object. Objects then use this mediator object to communicate with each other instead of communicating directly.
  10. Memento Pattern: allows restoring an object to its previous state.
  11. State Pattern: allows to implement Finite-State Machine using the Object Oriented Approach, object behavior changes when its internal state changes.

Summary

GoF Design Patterns provide a set of best practices and solutions for common problems that occur in software development. These patterns are widely used by software developers and have many benefits, including improved reliability, maintainability, and communication. However, they are not without criticism and must be used appropriately to avoid complexity and maintainability issues. The patterns are divided into three categories: creational, structural, and behavioral, each providing a set of solutions for specific types of problems.

References