Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.
Introduction
What are software design patterns?
Software design patterns are general, reusable solutions to commonly occurring problems in software development.
Design patterns are not libraries or frameworks that you can plug in and use right away. Rather, they are established suggested ways of thinking to use when you're faced with a problem that many developers have solved before.
In essence, design patterns help avoid reinventing the wheel. Along with anti-patterns and architectural patterns, they form an established vocabulary that software engineers often use to discuss common problems and solutions.
It's important to have an understanding of various patterns and know where to use them. However, it's equally important to avoid implementing them all over the place, which may reduce readability and maintainability without providing a noticeable benefit.
Where Do Design Patterns Come From?
Where do design patterns come from?
The idea of design patterns -- best practices to solve similar problems in different contexts -- was originally developed by architects. Adopted and reimagined in software engineering in the late 1980s, design patterns gained popularity when they were collected and codified in Design Patterns: Elements of Reusable Object-Oriented Software, a 1994 book by Gamma, Helm, Johnson and Vlissides. The four authors have since been commonly referred to as the Gang Of Four (GoF).
Design patterns have gained significant traction in Java and C# communities, but they can be used in all object oriented languages.
How Many Patterns Are There?
How many patterns are there?
The original Gang of Four book included 23 patterns divided into 3 categories:
- Creational patterns used to create objects without instantiating them directly. This category includes Abstract Factory, Builder, Factory Method, Prototype, and Singleton.
- Structural patterns that define ways of class and object composition. Adapter, Bridge, Composite, Decorator, Facade, Flyweight, and Proxy are all structural patterns.
- Behavioral patterns that suggest various ways for objects to communicate and/or evolve. Behavioral patterns include Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, and Visitor.
This is the original list, but it's not set in stone. If you look around, you will find variations where some patterns are omitted and others are introduced.
Selected design patterns
Some software engineers argue that the most common design pattern is The Big Ball of Mud. However, we'll look at a selection of patterns that are arguably more useful. We'll use C# as the language of choice for sample code.
Singleton
Singleton
One of the most used design patterns, Singleton makes sure that only one instance of a particular class is created, and provides a global point of access to that instance.
Singleton is most useful when you need a single object to coordinate other objects.
A singleton is usually implemented as a class that contains:
- a private constructor (so that only members of the same class can access it),
- a private static member,
- a static public method that returns a reference to the private static member.
Here's a class that represents the president as a singleton. Note the private static property holding the instance, the private constructor that can only be called from inside the class, and the GetInstance() method that creates a single instance representing the president if it's null, or returns the previously created instance if it's not null:
public class President
{
private static President instance;
private President(string firstName, string lastName, string party)
{
FirstName = firstName;
LastName = lastName;
Party = party;
}
public static President GetInstance()
{
if (instance == null) instance = new President("Donald", "Trump", "Republican");
return instance;
}
public string LastName { get; }
public string FirstName { get; }
public string Party { get; }
public override string ToString() => $"{FirstName} {LastName}, {Party}";
}
public class President {
private static President instance;
private President(String firstName, String lastName, String party) {
this.FirstName = firstName;
this.LastName = lastName;
this.Party = party;
}
public static President GetInstance() {
if (instance == null) instance = new President("Donald", "Trump", "Republican");
return instance;
}
private String LastName;
public String getLastName() { return LastName; }
private String FirstName;
public String getFirstName() { return FirstName; }
private String Party;
public String getParty() { return Party; }
@Override
public String toString() { return FirstName + " " + LastName + ", " + Party; }
}
class President {
constructor(firstName, lastName, party) {
this.FirstName = firstName;
this.LastName = lastName;
this.Party = party;
}
static GetInstance() {
if (!this.instance) this.instance = new President("Donald", "Trump", "Republican");
return this.instance;
}
get LastName() { return this.LastName; }
get FirstName() { return this.FirstName; }
get Party() { return this.Party; }
toString() { return this.FirstName + " " + this.LastName + ", " + this.Party; }
}
class President:
instance = None
def __init__(self, firstName, lastName, party):
self.FirstName = firstName
self.LastName = lastName
self.Party = party
@staticmethod
def GetInstance():
if President.instance == None:
President.instance = President("Donald", "Trump", "Republican")
return President.instance
@property
def LastName(self):
return self.LastName
@property
def FirstName(self):
return self.FirstName
@property
def Party(self):
return self.Party
def __str__(self):
return self.FirstName + " " + self.LastName + ", " + self.Party
#include <string>
class President {
private:
static President* instance;
std::string FirstName;
std::string LastName;
std::string Party;
President(std::string firstName, std::string lastName, std::string party)
: FirstName(firstName), LastName(lastName), Party(party) {}
public:
static President* GetInstance() {
if (instance == nullptr) {
instance = new President("Donald", "Trump", "Republican");
}
return instance;
}
std::string getLastName() { return LastName; }
std::string getFirstName() { return FirstName; }
std::string getParty() { return Party; }
std::string toString() { return FirstName + " " + LastName + ", " + Party; }
};
President* President::instance = nullptr;
package main
import (
"fmt"
)
type President struct {
FirstName string
LastName string
Party string
}
var instance *President
func GetInstance() *President {
if instance == nil {
instance = &President{"Donald", "Trump", "Republican"}
}
return instance
}
func (p *President) String() string {
return p.FirstName + " " + p.LastName + ", " + p.Party
}
func main() {
president := GetInstance()
fmt.Println(president)
}
At call site, we'd call the GetInstance() method to get the one and only instance. Note how trying to use the constructor directly doesn't compile because the constructor is private.
President constructed = new President("Bernie", "Sanders", "Democratic");
// The above won't compile because the constructor is private
President one = President.GetInstance();
President two = President.GetInstance();
Console.WriteLine(one);
Console.WriteLine(two);
Console.WriteLine(one == two); // Will return true because both variables point to the same instance
// In Java
President constructed = new President("Bernie", "Sanders", "Democratic"); // Won't compile, constructor is private
President one = President.getInstance();
President two = President.getInstance();
System.out.println(one);
System.out.println(two);
System.out.println(one == two); // Will return true as both variables point to the same instance
// In JavaScript
let constructed = new President("Bernie", "Sanders", "Democratic"); // Won't compile, constructor is private
let one = President.getInstance();
let two = President.getInstance();
console.log(one);
console.log(two);
console.log(one === two); // Will return true as both variables point to the same instance
# In Python
constructed = President("Bernie", "Sanders", "Democratic") # Won't work, constructor is private
one = President.get_instance()
two = President.get_instance()
print(one)
print(two)
print(one is two) # Will return True as both variables point to the same instance
// In C++
President constructed("Bernie", "Sanders", "Democratic"); // Won't compile, constructor is private
President& one = President::GetInstance();
President& two = President::GetInstance();
std::cout << &one << std::endl;
std::cout << &two << std::endl;
std::cout << (&one == &two) << std::endl; // Will return true as both variables point to the same instance
// In Go
constructed := NewPresident("Bernie", "Sanders", "Democratic") // Won't work, constructor is private
one := GetInstance()
two := GetInstance()
fmt.Println(one)
fmt.Println(two)
fmt.Println(one == two) // Will return true as both variables point to the same instance
There are multiple legitimate scenarios for using singletons, including caching, logging, and hardware interface access.
Singleton Instance Lifecycle
Before moving on, lock in the Singleton mental model: many callers may ask for the object, but the class controls whether a new instance is created.
In the visual, notice the split between the first request and later requests. That is the whole pattern: create once, then return the same shared reference.

Singleton Access Path
Before moving on, lock in the Singleton mental model: many callers may ask for the object, but they all go through one controlled access point.
In the visual, notice the two guarantees: the constructor is blocked from normal callers, and GetInstance() always routes requests back to the same stored instance.

Pattern Landscape Checkpoint
Before moving on, anchor the first pattern in the bigger map.
Design patterns are not a checklist to memorize. They are reusable names for common design pressures: How do we create objects? How do we compose them? How do they communicate? Singleton belongs to the creation side, but with a very specific constraint: exactly one shared instance.

Factory
Factory
The Factory pattern helps create an instance of an object without exposing the instantiation logic to the code that needs the object. It's best used with objects that are not trivial to create.
Normally, when you create an object, you just call a class constructor directly:
MyObject myObject = new MyObject();
// In Java
MyObject myObject = new MyObject();
// In JavaScript
const myObject = new MyObject();
# In Python
my_object = MyObject()
// In C++
MyObject myObject;
// In Go
myObject := NewMyObject()
Most of the time, there's nothing wrong with creating objects via constructors. However, sometimes you may want to make the parts of your code that need objects independent from the implementation of these objects. That's when Factory can help.
When using Factory, you don't call a constructor. Instead, you call a method in a static factory class and provide information about the type of object that you need. The factory returns an instance of the object you're looking for.
Here's a sample where you have a class representing a tennis ball and a static class for a factory that delivers a tennis ball:
public interface ITennisBall
{
int GetDiameter();
string GetMake();
}
public class GrassCourtTennisBall : ITennisBall
{
private string Color { get; set; }
private int Diameter { get; set; }
private int Bounce { get; set; }
private string Make { get; set; }
public GrassCourtTennisBall()
{
Color = "Optic yellow";
Diameter = 67;
Bounce = 145;
Make = "Slazenger Wimbledon";
}
public int GetDiameter() => Diameter;
public string GetMake() => Make;
}
public static class TennisBallFactory
{
public static ITennisBall DeliverBall() => new GrassCourtTennisBall();
}
public interface ITennisBall {
public int getDiameter();
public String getMake();
}
public class GrassCourtTennisBall implements ITennisBall {
private String color;
private int diameter;
private int bounce;
private String make;
public GrassCourtTennisBall() {
this.color = "Optic yellow";
this.diameter = 67;
this.bounce = 145;
this.make = "Slazenger Wimbledon";
}
public int getDiameter() { return this.diameter; }
public String getMake() { return this.make; }
}
public class TennisBallFactory {
public static ITennisBall deliverBall() {
return new GrassCourtTennisBall();
}
}
class ITennisBall {
getDiameter() {};
getMake() {};
}
class GrassCourtTennisBall extends ITennisBall {
constructor() {
super();
this.color = "Optic yellow";
this.diameter = 67;
this.bounce = 145;
this.make = "Slazenger Wimbledon";
}
getDiameter() { return this.diameter; }
getMake() { return this.make; }
}
const TennisBallFactory = {
deliverBall: function() { return new GrassCourtTennisBall(); }
}
class ITennisBall:
def get_diameter(self):
pass
def get_make(self):
pass
class GrassCourtTennisBall(ITennisBall):
def __init__(self):
self.color = "Optic yellow"
self.diameter = 67
self.bounce = 145
self.make = "Slazenger Wimbledon"
def get_diameter(self):
return self.diameter
def get_make(self):
return self.make
class TennisBallFactory:
@staticmethod
def deliver_ball():
return GrassCourtTennisBall()
class ITennisBall {
public:
virtual int getDiameter() = 0;
virtual std::string getMake() = 0;
};
class GrassCourtTennisBall : public ITennisBall {
private:
std::string color;
int diameter;
int bounce;
std::string make;
public:
GrassCourtTennisBall()
: color("Optic yellow"), diameter(67), bounce(145), make("Slazenger Wimbledon") {}
int getDiameter() override { return this->diameter; }
std::string getMake() override { return this->make; }
};
class TennisBallFactory {
public:
static ITennisBall* deliverBall() { return new GrassCourtTennisBall(); }
};
type ITennisBall interface {
getDiameter() int
getMake() string
}
type GrassCourtTennisBall struct {
color string
diameter int
bounce int
make string
}
func newGrassCourtTennisBall() *GrassCourtTennisBall {
return &GrassCourtTennisBall{
color: "Optic yellow",
diameter: 67,
bounce: 145,
make: "Slazenger Wimbledon",
}
}
func (g GrassCourtTennisBall) getDiameter() int {
return g.diameter
}
func (g GrassCourtTennisBall) getMake() string {
return g.make
}
type TennisBallFactory struct {}
func (TennisBallFactory) deliverBall() ITennisBall {
return newGrassCourtTennisBall()
}
At call site, instead of calling a constructor for TennisBall, you use TennisBallFactory to deliver the ball:
ITennisBall ball = TennisBallFactory.DeliverBall();
Console.WriteLine($"This is a {ball.GetMake()} ball with a diameter of {ball.GetDiameter()} mm");
ITennisBall ball = TennisBallFactory.DeliverBall();
Console.WriteLine($"This is a {ball.GetMake()} ball with a diameter of {ball.GetDiameter()} mm");
ITennisBall ball = TennisBallFactory.deliverBall();
System.out.println("This is a " + ball.getMake() + " ball with a diameter of " + ball.getDiameter() + " mm");
let ball = TennisBallFactory.deliverBall();
console.log(`This is a ${ball.getMake()} ball with a diameter of ${ball.getDiameter()} mm`);
ball = TennisBallFactory.deliver_ball()
print(f'This is a {ball.get_make()} ball with a diameter of {ball.get_diameter()} mm')
ITennisBall* ball = TennisBallFactory::DeliverBall();
std::cout << "This is a " << ball->GetMake() << " ball with a diameter of " << ball->GetDiameter() << " mm" << std::endl;
ball := TennisBallFactory.DeliverBall()
fmt.Printf("This is a %v ball with a diameter of %v mm\n", ball.GetMake(), ball.GetDiameter())
This is a simplified version of the Factory Method pattern. Another related pattern is Abstract Factory.
Factory Responsibility Split
Factory is mostly about moving object-selection logic out of the caller.
The caller says what it needs. The factory decides which concrete class to build. That separation is what keeps the rest of the code from depending on every construction detail.

Factory Decision Point
Factory is about moving object-choice logic out of the caller. The caller asks for what it needs; the factory decides which concrete class to return.
Use the visual to compare the coupling: direct construction ties the caller to a class, while Factory ties the caller to a creation interface or method.

Factory Flow
Factory is easiest to remember as a routing layer for object creation.
The calling code asks for a capability or type. The factory decides which concrete class to instantiate. That keeps the caller from depending on every constructor detail.

Builder
Builder
The Builder pattern helps... well, build objects! It can be useful in two situations:
- You have an object that could go in a variety of flavors.
- An object uses a non-trivial, multi-step creation process.
When an object could go in a variety of flavors, a common solution is to use a constructor with many parameters that define whether each of these flavors should be used:
public Pizza(int size, bool salami, bool mozzarella, bool ruccola) { }
// In Java
public Pizza(int size, boolean salami, boolean mozzarella, boolean ruccola) { }
// In JavaScript
constructor(size, salami, mozzarella, ruccola) { }
# In Python
def __init__(self, size, salami, mozzarella, ruccola):
pass
// In C++
public:
Pizza(int size, bool salami, bool mozzarella, bool ruccola) { }
// In Go
func NewPizza(size int, salami bool, mozzarella bool, ruccola bool) *Pizza {
return &Pizza{}
}
An alternative solution is to use the Builder pattern where a separate builder class manages adding flavors to an object and provides methods that make adding flavors explicit.
In the code below, we have two classes: Pizza represents a pizza but isn't intended to be used to construct pizzas, and PizzaBuilder is there for the sole purpose of constructing a custom pizza with a set of possible flavors:
class Pizza
{
public int Size { get; }
public bool Salami { get; }
public bool Mozzarella { get; }
public bool Ruccola { get; }
public Pizza(PizzaBuilder builder)
{
Size = builder.Size;
Salami = builder.Salami;
Mozzarella = builder.Mozzarella;
Ruccola = builder.Ruccola;
}
public string GetDescription() => $"This is a {Size} cm pizza. ";
}
class PizzaBuilder
{
public int Size;
public bool Salami { get; set; }
public bool Mozzarella { get; set; }
public bool Ruccola { get; set; }
public PizzaBuilder(int size) => Size = size;
public PizzaBuilder AddSalami()
{
Salami = true;
return this;
}
public PizzaBuilder AddMozzarella()
{
Mozzarella = true;
return this;
}
public PizzaBuilder AddRuccola()
{
Ruccola = true;
return this;
}
public Pizza Build() => new Pizza(this);
}
public class Pizza {
private int size;
private boolean salami;
private boolean mozzarella;
private boolean ruccola;
public Pizza(PizzaBuilder builder) {
this.size = builder.size;
this.salami = builder.salami;
this.mozzarella = builder.mozzarella;
this.ruccola = builder.ruccola;
}
public String getDescription() {
return "This is a " + size + " cm pizza.";
}
public static class PizzaBuilder {
private int size;
private boolean salami;
private boolean mozzarella;
private boolean ruccola;
public PizzaBuilder(int size) {
this.size = size;
}
public PizzaBuilder addSalami() {
this.salami = true;
return this;
}
public PizzaBuilder addMozzarella() {
this.mozzarella = true;
return this;
}
public PizzaBuilder addRuccola() {
this.ruccola = true;
return this;
}
public Pizza build() {
return new Pizza(this);
}
}
}
class Pizza {
constructor(builder) {
this.size = builder.size;
this.salami = builder.salami;
this.mozzarella = builder.mozzarella;
this.ruccola = builder.ruccola;
}
getDescription() {
return `This is a ${this.size} cm pizza.`;
}
}
class PizzaBuilder {
constructor(size) {
this.size = size;
this.salami = false;
this.mozzarella = false;
this.ruccola = false;
}
addSalami() {
this.salami = true;
return this;
}
addMozzarella() {
this.mozzarella = true;
return this;
}
addRuccola() {
this.ruccola = true;
return this;
}
build() {
return new Pizza(this);
}
}
class Pizza:
def __init__(self, builder):
self.size = builder.size
self.salami = builder.salami
self.mozzarella = builder.mozzarella
self.ruccola = builder.ruccola
def get_description(self):
return f"This is a {self.size} cm pizza."
class PizzaBuilder:
def __init__(self, size):
self.size = size
self.salami = False
self.mozzarella = False
self.ruccola = False
def add_salami(self):
self.salami = True
return self
def add_mozzarella(self):
self.mozzarella = True
return self
def add_ruccola(self):
self.ruccola = True
return self
def build(self):
return Pizza(self)
#include <string>
#include <sstream>
class Pizza {
public:
class PizzaBuilder;
private:
int size;
bool salami;
bool mozzarella;
bool ruccola;
public:
Pizza(const PizzaBuilder& builder);
std::string getDescription() const;
};
class Pizza::PizzaBuilder {
public:
int size;
bool salami;
bool mozzarella;
bool ruccola;
public:
PizzaBuilder(int size);
PizzaBuilder& addSalami();
PizzaBuilder& addMozzarella();
PizzaBuilder& addRuccola();
Pizza build() const;
};
Pizza::Pizza(const PizzaBuilder& builder) :
size(builder.size), salami(builder.salami), mozzarella(builder.mozzarella), ruccola(builder.ruccola) {}
std::string Pizza::getDescription() const {
std::stringstream ss;
ss << "This is a " << size << " cm pizza.";
return ss.str();
}
Pizza::PizzaBuilder::PizzaBuilder(int size) : size(size), salami(false), mozzarella(false), ruccola(false) {}
Pizza::PizzaBuilder& Pizza::PizzaBuilder::addSalami() {
salami = true;
return *this;
}
Pizza::PizzaBuilder& Pizza::PizzaBuilder::addMozzarella() {
mozzarella = true;
return *this;
}
Pizza::PizzaBuilder& Pizza::PizzaBuilder::addRuccola() {
ruccola = true;
return *this;
}
Pizza Pizza::PizzaBuilder::build() const {
return Pizza(*this);
}
type Pizza struct {
size int
salami bool
mozzarella bool
ruccola bool
}
func (p Pizza) getDescription() string {
return fmt.Sprintf("This is a %v cm pizza.", p.size)
}
type PizzaBuilder struct {
size int
salami bool
mozzarella bool
ruccola bool
}
func newPizzaBuilder(size int) *PizzaBuilder {
return &PizzaBuilder{size: size}
}
func (pb *PizzaBuilder) addSalami() *PizzaBuilder {
pb.salami = true
return pb
}
func (pb *PizzaBuilder) addMozzarella() *PizzaBuilder {
pb.mozzarella = true
return pb
}
func (pb *PizzaBuilder) addRuccola() *PizzaBuilder {
pb.ruccola = true
return pb
}
func (pb *PizzaBuilder) build() Pizza {
return Pizza{
size: pb.size,
salami: pb.salami,
mozzarella: pb.mozzarella,
ruccola: pb.ruccola,
}
}
At call site, we create a pizza via PizzaBuilder that makes the customization process very explicit:
Pizza pizza = new PizzaBuilder(28)
.AddMozzarella()
.AddSalami()
.AddRuccola()
.Build();
Console.WriteLine(pizza.GetDescription());
// In Java
Pizza pizza = new PizzaBuilder(28)
.addMozzarella()
.addSalami()
.addRuccola()
.build();
System.out.println(pizza.getDescription());
// In JavaScript
const pizza = new PizzaBuilder(28)
.addMozzarella()
.addSalami()
.addRuccola()
.build();
console.log(pizza.getDescription());
# In Python
pizza = PizzaBuilder(28)\
.add_mozzarella()\
.add_salami()\
.add_ruccola()\
.build()
print(pizza.get_description())
// In C++
Pizza pizza = PizzaBuilder(28)
.AddMozzarella()
.AddSalami()
.AddRuccola()
.Build();
std::cout << pizza.GetDescription() << std::endl;
// In Go
pizza := NewPizzaBuilder(28).
AddMozzarella().
AddSalami().
AddRuccola().
Build()
fmt.Println(pizza.GetDescription())
Note that many programming languages allow using named parameters on call sites, which can be a valid alternative to using the Builder pattern in some cases:
var pizza = new Pizza(size: 28, mozzarella: true, salami: false, ruccola: true);
Pizza pizza = new Pizza(28, true, false, true);
var pizza = new Pizza(28, true, false, true);
pizza = Pizza(28, True, False, True)
Pizza pizza(28, true, false, true);
pizza := Pizza{28, true, false, true}
However, if a builder goes beyond adding parameters to an object and applies custom logic on each step of object construction, then named parameters aren't a viable replacement.
Builder Over Constructor Flags
Builder is easiest to remember as a readability upgrade for complex creation.
Instead of passing a long row of positional values, each construction step gets a name. The object becomes easier to configure, easier to review, and harder to accidentally misuse.

Builder Step Ladder
Builder is useful when construction has choices or steps that should be readable. Instead of decoding a long parameter list, you compose the object one decision at a time.
In the visual, focus on how each step names intent: size, toppings, then final build.

Builder Mental Model
Builder is about making construction readable when an object has many optional parts or setup steps.
Instead of passing a long row of flags into one constructor, the builder turns object creation into a small sequence of named decisions.

Adapter
Adapter
If two classes cannot work together because their interfaces are different, applying the Adapter pattern helps establish compatibility between them.
For example, if one class has a function that returns XML and another class has a function that takes JSON as input, and you want to take advantage of functionality in both of these classes, you may want to implement the Adapter pattern to enable interaction between them.
It's very similar to how adapters work with consumer electronics:
- To read a memory card from your laptop, you use an adapter.
- To plug your European laptop into a socket in the US, you use an adapter.
Speaking of US sockets, here's an example where we use the USSocketAdapter class to make the USSocket class compatible with the ISocket interface:
interface ISocket
{
void Charge();
}
class GermanSocket : ISocket
{
public void Charge() { }
}
class BritishSocket : ISocket
{
public void Charge() { }
}
class Laptop
{
public void Charge(ISocket socket) { }
}
class USSocket
{
public void Magic() { }
}
class USSocketAdapter : ISocket
{
private USSocket AmericanSocket { get; set; }
public USSocketAdapter(USSocket socket) => AmericanSocket = socket;
public void Charge() => AmericanSocket.Magic();
}
interface ISocket
{
void Charge();
}
class GermanSocket implements ISocket
{
public void Charge() { }
}
class BritishSocket implements ISocket
{
public void Charge() { }
}
class Laptop
{
public void Charge(ISocket socket) { }
}
class USSocket
{
public void Magic() { }
}
class USSocketAdapter implements ISocket
{
private USSocket AmericanSocket;
public USSocketAdapter(USSocket socket) { AmericanSocket = socket;}
public void Charge() { AmericanSocket.Magic();}
}
class ISocket {
Charge() {}
}
class GermanSocket extends ISocket {
Charge() {}
}
class BritishSocket extends ISocket {
Charge() {}
}
class Laptop {
Charge(socket) {}
}
class USSocket {
Magic() {}
}
class USSocketAdapter extends ISocket {
constructor(socket) {
super();
this.AmericanSocket = socket;
}
Charge() {
this.AmericanSocket.Magic();
}
}
class ISocket:
def Charge(self):
pass
class GermanSocket(ISocket):
def Charge(self):
pass
class BritishSocket(ISocket):
def Charge(self):
pass
class Laptop:
def Charge(self, socket):
pass
class USSocket:
def Magic(self):
pass
class USSocketAdapter(ISocket):
def __init__(self, socket):
self.AmericanSocket = socket
def Charge(self):
self.AmericanSocket.Magic()
class ISocket
{
public:
virtual void Charge() = 0;
};
class GermanSocket : public ISocket
{
public:
void Charge() override { }
};
class BritishSocket : public ISocket
{
public:
void Charge() override { }
};
class Laptop
{
public:
void Charge(ISocket* socket) { }
};
class USSocket
{
public:
void Magic() { }
};
class USSocketAdapter : public ISocket
{
private:
USSocket* AmericanSocket;
public:
USSocketAdapter(USSocket* socket) : AmericanSocket(socket) { }
void Charge() override { AmericanSocket->Magic(); }
};
type ISocket interface {
Charge()
}
type GermanSocket struct{}
func (GermanSocket) Charge() {}
type BritishSocket struct{}
func (BritishSocket) Charge() {}
type Laptop struct{}
func (Laptop) Charge(socket ISocket) {}
type USSocket struct{}
func (u USSocket) Magic() {}
type USSocketAdapter struct{
AmericanSocket *USSocket
}
func (usa *USSocketAdapter) Charge() {usa.AmericanSocket.Magic()}
At call site, we can now wrap a US socket into the socket adapter and charge our laptop:
var americanSocket = new USSocket();
var americanSocketAdapter = new USSocketAdapter(americanSocket);
var laptop = new Laptop();
laptop.Charge(americanSocketAdapter);
// In Java
USSocket americanSocket = new USSocket();
USSocketAdapter americanSocketAdapter = new USSocketAdapter(americanSocket);
Laptop laptop = new Laptop();
laptop.charge(americanSocketAdapter);
// In JavaScript
const americanSocket = new USSocket();
const americanSocketAdapter = new USSocketAdapter(americanSocket);
const laptop = new Laptop();
laptop.charge(americanSocketAdapter);
# In Python
american_socket = USSocket()
american_socket_adapter = USSocketAdapter(american_socket)
laptop = Laptop()
laptop.charge(american_socket_adapter)
// In C++
USSocket americanSocket;
USSocketAdapter americanSocketAdapter(americanSocket);
Laptop laptop;
laptop.Charge(americanSocketAdapter);
// In Go
americanSocket := NewUSSocket()
americanSocketAdapter := NewUSSocketAdapter(americanSocket)
laptop := NewLaptop()
laptop.Charge(americanSocketAdapter)
A related pattern is Facade that provides a simplified interface to a complex subsystem. However, while Facade creates a new, simpler interface, Adapter serves to design to an existing interface.
Strategy
Strategy
The Strategy pattern helps define a family of related algorithms, and make decisions to use one or another based on user input when the application runs.
One benefit of using Strategy is that it helps avoid complex chains of conditions for switching between algorithms, replacing them with delegation to different Strategy objects.
For example, in an order processing system, prices will probably be calculated in different ways depending on customer type, purchase quantity, and other factors. In this situation, using Strategy can help select the right pricing algorithm at runtime.
Another example would be a tennis game where defence and offence are two distinct strategies:
interface IMatchStrategy
{
Shot HitTheBall();
}
class DefensiveMatchStrategy : IMatchStrategy
{
public Shot HitTheBall()
{
return new Shot
{
Type = ShotTypes.Slice,
Power = 0.7
};
}
}
class AggressiveMatchStrategy : IMatchStrategy
{
public Shot HitTheBall()
{
return new Shot
{
Type = ShotTypes.Flat,
Power = 1.2
};
}
}
interface IMatchStrategy
{
Shot hitTheBall();
}
class DefensiveMatchStrategy implements IMatchStrategy
{
public Shot hitTheBall()
{
return new Shot
{
Type = ShotTypes.Slice,
Power = 0.7
};
}
}
class AggressiveMatchStrategy implements IMatchStrategy
{
public Shot hitTheBall()
{
return new Shot
{
Type = ShotTypes.Flat,
Power = 1.2
};
}
}
class IMatchStrategy
{
hitTheBall() {}
}
class DefensiveMatchStrategy extends IMatchStrategy
{
hitTheBall()
{
return {
Type : "Slice",
Power: 0.7
}
}
}
class AggressiveMatchStrategy extends IMatchStrategy
{
hitTheBall()
{
return {
Type : "Flat",
Power: 1.2
}
}
}
from abc import ABC, abstractmethod
class IMatchStrategy(ABC):
@abstractmethod
def hit_the_ball(self):
pass
class DefensiveMatchStrategy(IMatchStrategy):
def hit_the_ball(self):
return {
"Type": "Slice",
"Power": 0.7
}
class AggressiveMatchStrategy(IMatchStrategy):
def hit_the_ball(self):
return {
"Type": "Flat",
"Power": 1.2
}
}
class IMatchStrategy {
public:
virtual Shot hitTheBall() = 0; // pure virtual function
};
class DefensiveMatchStrategy : public IMatchStrategy {
public:
Shot hitTheBall() {
return Shot(ShotTypes::Slice, 0.7);
}
};
class AggressiveMatchStrategy : public IMatchStrategy {
public:
Shot hitTheBall() {
return Shot(ShotTypes::Flat, 1.2);
}
};
type IMatchStrategy interface {
HitTheBall() Shot
}
type DefensiveMatchStrategy struct {}
func (d DefensiveMatchStrategy) HitTheBall() Shot {
return Shot{
TypeIs: "Slice",
Power: 0.7,
}
}
type AggressiveMatchStrategy struct {}
func (a AggressiveMatchStrategy) HitTheBall() Shot {
return Shot{
TypeIs: "Flat",
Power: 1.2,
}
}
At call site, we can start a match aggressively, but then switch to defense depending on external factors:
Match match = new Match(new AggressiveMatchStrategy());
Shot shot1 = match.HitTheBall();
Console.WriteLine(match.MatchStrategy);
Console.WriteLine(shot1.ToString());
if (new Random().Next(1, 10) > 5)
match.MatchStrategy = new DefensiveMatchStrategy();
Shot shot2 = match.HitTheBall();
Console.WriteLine(match.MatchStrategy);
Console.WriteLine(shot2.ToString());
Match match = new Match(new AggressiveMatchStrategy());
Shot shot1 = match.hitTheBall();
System.out.println(match.getMatchStrategy());
System.out.println(shot1.toString());
if (new Random().nextInt(1, 10) > 5)
match.setMatchStrategy(new DefensiveMatchStrategy());
Shot shot2 = match.hitTheBall();
System.out.println(match.getMatchStrategy());
System.out.println(shot2.toString());
let match = new Match(new AggressiveMatchStrategy());
let shot1 = match.hitTheBall();
console.log(match.matchStrategy);
console.log(shot1.toString());
if (Math.floor(Math.random() * (10 - 1) + 1) > 5)
match.matchStrategy = new DefensiveMatchStrategy();
let shot2 = match.hitTheBall();
console.log(match.matchStrategy);
console.log(shot2.toString());
match = Match(AggressiveMatchStrategy())
shot1 = match.hit_the_ball()
print(match.match_strategy)
print(shot1.__str__())
if random.randint(1, 10) > 5:
match.match_strategy = DefensiveMatchStrategy()
shot2 = match.hit_the_ball()
print(match.match_strategy)
print(shot2.__str__())
Match match = Match(AggressiveMatchStrategy());
Shot shot1 = match.hitTheBall();
std::cout << match.getMatchStrategy() << '\n';
std::cout << shot1.toString() << '\n';
if ((rand() % 10 + 1) > 5)
match.setMatchStrategy(DefensiveMatchStrategy());
Shot shot2 = match.hitTheBall();
std::cout << match.getMatchStrategy() << '\n';
std::cout << shot2.toString() << '\n';
match := Match{matchStrategy: AggressiveMatchStrategy{}}
shot1 := match.hitTheBall()
fmt.Println(match.matchStrategy)
fmt.Println(shot1.toString())
if rand.Intn(10-1)+1 > 5 {
match.matchStrategy = DefensiveMatchStrategy{}
}
shot2 := match.hitTheBall()
fmt.Println(match.matchStrategy)
fmt.Println(shot2.toString())
A popular technique related to the Strategy pattern is Dependency Injection.
Observer
Observer
The Observer pattern helps objects learn about changes to the state of other objects. It does so by establishing a one-to-many dependency between objects. It's used extensively for event management.
You use the pattern when you have a critical object ("the subject"), a group of objects that depend on it ("observers"), and you need to make sure that observers are notified of any changes to the subject -- typically by calling one of their methods. Following this pattern helps circulate updates between objects without unnecessarily coupling them, and makes adding new observers easy.
Have you ever visited a website of a product that was yet to launch? Have you subscribed to receive an e-mail as soon as the product becomes publicly available? If you have, you acted as an observer, and the product acted as the subject.
Here's an example where a carsharing pool is the subject and implements the IObservable interface from C#'s Base Class Library, and a driver who uses the carsharing service is an observer and implements the IObserver interface:
class SharedCar
{
public string Model { get; }
public SharedCar(string model) => Model = model;
}
class CarsharingCustomer : IObserver<SharedCar>
{
public string Name { get; }
public CarsharingCustomer(string name) => Name = name;
public void OnCompleted() => throw new NotImplementedException();
public void OnError(Exception error) => throw new NotImplementedException();
public void OnNext(SharedCar value) => Console.WriteLine($"Hey {Name}, a new car is available: {value.Model}");
}
class CarsharingPool : IObservable<SharedCar>
{
public List<IObserver<SharedCar>> Observers { get; }
public List<SharedCar> AvailableCars { get; }
public CarsharingPool()
{
Observers = new List<IObserver<SharedCar>>();
AvailableCars = new List<SharedCar>();
}
public IDisposable Subscribe(IObserver<SharedCar> observer)
{
if (!Observers.Contains(observer)) Observers.Add(observer);
return new Unsubscriber<SharedCar>(Observers, observer);
}
private void Notify(SharedCar sharedCar)
{
foreach(var observer in Observers)
{
observer.OnNext(sharedCar);
}
}
public void AddCar(SharedCar sharedCar)
{
AvailableCars.Add(sharedCar);
Notify(sharedCar);
}
}
class SharedCar
{
private String model;
public SharedCar(String model) { this.model = model; }
public String getModel() { return model; }
}
class CarsharingCustomer implements Observer<SharedCar>
{
private String name;
public CarsharingCustomer(String name) { this.name = name; }
public void update(Observable o, SharedCar sharedCar)
{
System.out.println("Hey " + name + ", a new car is available: " + sharedCar.getModel());
}
}
class CarsharingPool extends Observable
{
private List<SharedCar> availableCars;
public CarsharingPool() {
availableCars = new ArrayList<>();
}
public void addCar(SharedCar sharedCar)
{
availableCars.add(sharedCar);
setChanged();
notifyObservers(sharedCar);
}
}
class SharedCar
{
constructor(model) { this.model = model; }
getModel() { return this.model; }
}
class CarsharingCustomer
{
constructor(name) { this.name = name; }
update(sharedCar) {
console.log(`Hey ${this.name}, a new car is available: ${sharedCar.getModel()}`);
}
}
class CarsharingPool
{
constructor() {
this.availableCars = [];
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
addCar(sharedCar)
{
this.availableCars.push(sharedCar);
this.observers.forEach(observer => observer.update(sharedCar));
}
}
class SharedCar:
def __init__(self, model):
self._model = model
def get_model(self):
return self._model
class CarsharingCustomer:
def __init__(self, name):
self._name = name
def update(self, shared_car):
print(f"Hey {self._name}, a new car is available: {shared_car.get_model()}")
class CarsharingPool:
def __init__(self):
self._available_cars = []
self._observers = []
def add_observer(self, observer):
self._observers.append(observer)
def add_car(self, shared_car):
self._available_cars.append(shared_car)
for observer in self._observers:
observer.update(shared_car)
class SharedCar
{
private:
string model;
public:
SharedCar(string model) { this->model = model; }
string getModel() { return model; }
};
class CarsharingCustomer
{
private:
string name;
public:
CarsharingCustomer(string name) { this->name = name; }
void update(SharedCar sharedCar) {
cout << "Hey " << name << ", a new car is available: " << sharedCar.getModel() << endl;
}
};
class CarsharingPool
{
private:
vector<SharedCar> availableCars;
vector<CarsharingCustomer*> observers;
public:
CarsharingPool(){
observers = vector<CarsharingCustomer*>();
availableCars = vector<SharedCar>();
}
void addObserver(CarsharingCustomer* observer) {
observers.push_back(observer);
}
void addCar(SharedCar sharedCar)
{
availableCars.push_back(sharedCar);
for (auto observer : observers) {
observer->update(sharedCar);
}
}
};
type SharedCar struct {
Model string
}
func NewSharedCar(model string) *SharedCar {
return &SharedCar{Model:model}
}
type CarsharingCustomer struct {
Name string
}
func NewCarsharingCustomer(name string) *CarsharingCustomer {
return &CarsharingCustomer{Name:name}
}
func (c *CarsharingCustomer) Update(sharedCar SharedCar) {
fmt.Printf("Hey %s, a new car is available: %s\n", c.Name, sharedCar.Model)
}
type CarsharingPool struct {
AvailableCars []SharedCar
Observers []*CarsharingCustomer
}
func NewCarsharingPool() *CarsharingPool {
return &CarsharingPool{
AvailableCars: []SharedCar{},
Observers: []*CarsharingCustomer{},
}
}
func (p *CarsharingPool) AddObserver(observer *CarsharingCustomer) {
p.Observers = append(p.Observers, observer)
}
func (p *CarsharingPool) AddCar(sharedCar SharedCar) {
p.AvailableCars = append(p.AvailableCars, sharedCar)
for _, observer := range p.Observers {
observer.Update(sharedCar)
}
}
(The implementation above also uses a class called Unsubscriber that is omitted for brevity.)
At call site, the driver is subscribed to updates from the carsharing service, and gets a notification whenever a new shared car becomes available:
var driver = new CarsharingCustomer("Louise Sawyer");
var availableCars = new CarsharingPool();
availableCars.Subscribe(driver);
availableCars.AddCar(new SharedCar("Ford Thunderbird"));
CarsharingCustomer driver = new CarsharingCustomer("Louise Sawyer");
CarsharingPool availableCars = new CarsharingPool();
availableCars.subscribe(driver);
availableCars.addCar(new SharedCar("Ford Thunderbird"));
const driver = new CarsharingCustomer("Louise Sawyer");
const availableCars = new CarsharingPool();
availableCars.subscribe(driver);
availableCars.addCar(new SharedCar("Ford Thunderbird"));
driver = CarsharingCustomer("Louise Sawyer")
available_cars = CarsharingPool()
available_cars.subscribe(driver)
available_cars.add_car(SharedCar("Ford Thunderbird"))
CarsharingCustomer driver("Louise Sawyer");
CarsharingPool availableCars;
availableCars.subscribe(driver);
availableCars.addCar(SharedCar("Ford Thunderbird"));
driver := CarsharingCustomer("Louise Sawyer")
availableCars := CarsharingPool{}
availableCars.Subscribe(driver)
availableCars.AddCar(SharedCar("Ford Thunderbird"))
Visitor
Visitor
The Visitor pattern lets you add operations to objects without having to modify these objects -- in other words, it's a way of separating an algorithm from an object structure on which it operates. This pattern is used when:
- you have a complex structure that contains objects of different types,
- similar operations need to be performed on all these objects.
Visitor defines behavior in abstract classes or interfaces. This helps avoid the use of conditional blocks and type checks, which tend to become clunky and hard to maintain.
Below is a code sample where a sports fan (visitor) wants to visit the Olympic Games to watch competitions in various sports (visitees). All visitees have the Accept method that takes a parameter representing a visitor:
interface ISummerOlympicSport
{
void Accept(ICompetitionVisitor visitor);
}
class DivingCompetition : ISummerOlympicSport
{
public void Jump() { }
public void Accept(ICompetitionVisitor visitor) => visitor.WatchDiving(this);
}
class CyclingTrackRace : ISummerOlympicSport
{
public void Ride() { }
public void Accept(ICompetitionVisitor visitor) => visitor.WatchCycling(this);
}
class BadmintonMatch : ISummerOlympicSport
{
public void Hit() { }
public void Accept(ICompetitionVisitor visitor) => visitor.WatchBadminton(this);
}
interface SummerOlympicSport
{
void accept(CompetitionVisitor visitor);
}
class DivingCompetition implements SummerOlympicSport
{
public void jump() { }
public void accept(CompetitionVisitor visitor) { visitor.watchDiving(this); }
}
class CyclingTrackRace implements SummerOlympicSport
{
public void ride() { }
public void accept(CompetitionVisitor visitor) { visitor.watchCycling(this); }
}
class BadmintonMatch implements SummerOlympicSport
{
public void hit() { }
public void accept(CompetitionVisitor visitor) { visitor.watchBadminton(this); }
}
class SummerOlympicSport {
accept(visitor) { }
}
class DivingCompetition extends SummerOlympicSport {
jump() { }
accept(visitor) { visitor.watchDiving(this); }
}
class CyclingTrackRace extends SummerOlympicSport {
ride() { }
accept(visitor) { visitor.watchCycling(this); }
}
class BadmintonMatch extends SummerOlympicSport {
hit() { }
accept(visitor) { visitor.watchBadminton(this); }
}
from abc import ABC, abstractmethod
class SummerOlympicSport(ABC):
@abstractmethod
def accept(self, visitor): pass
class DivingCompetition(SummerOlympicSport):
def jump(self): pass
def accept(self, visitor): visitor.watchDiving(self)
class CyclingTrackRace(SummerOlympicSport):
def ride(self): pass
def accept(self, visitor): visitor.watchCycling(self)
class BadmintonMatch(SummerOlympicSport):
def hit(self): pass
def accept(self, visitor): visitor.watchBadminton(self)
class SummerOlympicSport
{
public:
virtual void accept(CompetitionVisitor& visitor) = 0;
};
class DivingCompetition : public SummerOlympicSport
{
public:
void jump() { }
void accept(CompetitionVisitor& visitor) override { visitor.watchDiving(*this); }
};
class CyclingTrackRace : public SummerOlympicSport
{
public:
void ride() { }
void accept(CompetitionVisitor& visitor) override { visitor.watchCycling(*this); }
};
class BadmintonMatch : public SummerOlympicSport
{
public:
void hit() { }
void accept(CompetitionVisitor& visitor) override { visitor.watchBadminton(*this); }
};
type SummerOlympicSport interface {
Accept(visitor CompetitionVisitor)
}
type DivingCompetition struct{}
func (d DivingCompetition) Jump() {}
func (d DivingCompetition) Accept(visitor CompetitionVisitor) { visitor.WatchDiving(d) }
type CyclingTrackRace struct{}
func (c CyclingTrackRace) Ride() {}
func (c CyclingTrackRace) Accept(visitor CompetitionVisitor) { visitor.WatchCycling(c) }
type BadmintonMatch struct{}
func (b BadmintonMatch) Hit() {}
func (b BadmintonMatch) Accept(visitor CompetitionVisitor) { visitor.WatchBadminton(b) }
Here's how the visitor is implemented:
interface ICompetitionVisitor
{
void WatchDiving(DivingCompetition divingCompetition);
void WatchCycling(CyclingTrackRace cyclingTrackRace);
void WatchBadminton(BadmintonMatch badmintonMatch);
}
class CompetitionVisitor : ICompetitionVisitor
{
public void WatchBadminton(BadmintonMatch badmintonMatch) => badmintonMatch.Hit();
public void WatchCycling(CyclingTrackRace cyclingTrackRace) => cyclingTrackRace.Ride();
public void WatchDiving(DivingCompetition divingCompetition) => divingCompetition.Jump();
}
// In Java
interface ICompetitionVisitor {
void watchDiving(DivingCompetition divingCompetition);
void watchCycling(CyclingTrackRace cyclingTrackRace);
void watchBadminton(BadmintonMatch badmintonMatch);
}
class CompetitionVisitor implements ICompetitionVisitor {
public void watchBadminton(BadmintonMatch badmintonMatch) {
badmintonMatch.hit();
}
public void watchCycling(CyclingTrackRace cyclingTrackRace) {
cyclingTrackRace.ride();
}
public void watchDiving(DivingCompetition divingCompetition) {
divingCompetition.jump();
}
}
// In JavaScript
class ICompetitionVisitor {
watchDiving(divingCompetition) {}
watchCycling(cyclingTrackRace) {}
watchBadminton(badmintonMatch) {}
}
class CompetitionVisitor extends ICompetitionVisitor {
watchBadminton(badmintonMatch) {
badmintonMatch.hit();
}
watchCycling(cyclingTrackRace) {
cyclingTrackRace.ride();
}
watchDiving(divingCompetition) {
divingCompetition.jump();
}
}
# In Python
from abc import ABC, abstractmethod
class ICompetitionVisitor(ABC):
@abstractmethod
def watch_diving(self, diving_competition): pass
@abstractmethod
def watch_cycling(self, cycling_track_race): pass
@abstractmethod
def watch_badminton(self, badminton_match): pass
class CompetitionVisitor(ICompetitionVisitor):
def watch_badminton(self, badminton_match):
badminton_match.hit()
def watch_cycling(self, cycling_track_race):
cycling_track_race.ride()
def watch_diving(self, diving_competition):
diving_competition.jump()
// In C++
class ICompetitionVisitor {
public:
virtual void WatchDiving(DivingCompetition divingCompetition) = 0;
virtual void WatchCycling(CyclingTrackRace cyclingTrackRace) = 0;
virtual void WatchBadminton(BadmintonMatch badmintonMatch) = 0;
};
class CompetitionVisitor : public ICompetitionVisitor {
public:
void WatchBadminton(BadmintonMatch badmintonMatch) {
badmintonMatch.Hit();
}
void WatchCycling(CyclingTrackRace cyclingTrackRace) {
cyclingTrackRace.Ride();
}
void WatchDiving(DivingCompetition divingCompetition) {
divingCompetition.Jump();
}
}
// In Go
type ICompetitionVisitor interface {
WatchDiving(divingCompetition DivingCompetition)
WatchCycling(cyclingTrackRace CyclingTrackRace)
WatchBadminton(badmintonMatch BadmintonMatch)
}
type CompetitionVisitor struct{}
func (cv CompetitionVisitor) WatchBadminton(badmintonMatch BadmintonMatch) {
badmintonMatch.Hit()
}
func (cv CompetitionVisitor) WatchCycling(cyclingTrackRace CyclingTrackRace) {
cyclingTrackRace.Ride()
}
func (cv CompetitionVisitor) WatchDiving(divingCompetition DivingCompetition) {
divingCompetition.Jump()
}
At call site, we can now initialize a visitor (competitionVisitor), and walk them through the list of visitees (competitionsToVisit) in a loop where we call the Accept() method that hides dissimilarity of the visitees:
var competitionsToVisit = new List<ISummerOlympicSport>
{
new DivingCompetition(), new BadmintonMatch(), new CyclingTrackRace()
};
var competitionVisitor = new CompetitionVisitor();
foreach (var competition in competitionsToVisit)
{
competition.Accept(competitionVisitor);
}
List<ISummerOlympicSport> competitionsToVisit = Arrays.asList(
new DivingCompetition(),
new BadmintonMatch(),
new CyclingTrackRace()
);
CompetitionVisitor competitionVisitor = new CompetitionVisitor();
for (ISummerOlympicSport competition : competitionsToVisit) {
competition.accept(competitionVisitor);
}
let competitionsToVisit = [
new DivingCompetition(),
new BadmintonMatch(),
new CyclingTrackRace()
];
let competitionVisitor = new CompetitionVisitor();
for (let competition of competitionsToVisit) {
competition.accept(competitionVisitor);
}
competitions_to_visit = [
DivingCompetition(),
BadmintonMatch(),
CyclingTrackRace()
]
competition_visitor = CompetitionVisitor()
for competition in competitions_to_visit:
competition.accept(competition_visitor)
vector<ISummerOlympicSport*> competitionsToVisit = {
new DivingCompetition,
new BadmintonMatch,
new CyclingTrackRace
};
CompetitionVisitor competitionVisitor;
for (auto& competition : competitionsToVisit) {
competition->accept(competitionVisitor);
}
competitionsToVisit := []ISummerOlympicSport{
DivingCompetition{},
BadmintonMatch{},
CyclingTrackRace{},
}
competitionVisitor := CompetitionVisitor{}
for _, competition := range competitionsToVisit {
competition.Accept(competitionVisitor)
}
There's a related pattern called Iterator. However, Iterator is used on collections that contain objects of the same type. Visitor has wider applicability and can be used on hierarchical and/or composite data structures.
Related Concepts
Related concepts
Where there are patterns, there should be anti-patterns. Indeed, a mere year after the original Gang of Four book, another influential book was released, AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis. The book covered bad practices in software development, software architecture, and project management. Some of the most well-known anti-patterns covered in the book include Spaghetti Code, Cut-and-Paste Programming, Vendor Lock-In, Architecture by Implication, Design by Committee, Reinvent the Wheel, Analysis Paralysis, and Death by Planning.
Interestingly, when design patterns get overused, many in the development community start to treat them as anti-patterns. The Singleton pattern is the primary example of such evolution.
In addition to design patterns, there are architectural patterns that are similar in spirit but have a broader scope and apply to the design of an application as a whole. Some of the most popular architectural patterns include Model-View-Controller (MVC), Model-View-ViewModel (MVVM), Enterprise Service Bus (ESB), and Peer-to-Peer (P2P).
Where To Learn More?
One Pager Cheat Sheet
One Pager Cheat Sheet
- Design Patterns are general and reusable solutions that help developers avoid
reinventing the wheel, and should beused judiciouslyto provide the most benefit. Design patterns were originally developed by architects, and gained popularity when codified by the "Gang of Four" in 1994 as elements of reusable object-oriented software.- There are 23 patterns from the Gang of Four book, divided into 3 categories (Creational, Structural and Behavioral) with The Big Ball of Mud being one of the most commonly used design patterns.
- The Singleton design pattern ensures that only one instance of a class is created, and provides a global point of access to that instance.
- - Singleton = one controlled instance plus one global access path.
- - Singleton = one shared instance, reached through one controlled access method.
- - GoF patterns group into Creational, Structural, and Behavioral; Singleton is a Creational pattern that controls instance count.
- The Factory Method pattern makes it easy to create instances of objects without exposing the instantiation logic, which is especially useful for non-trivial objects.
- - Factory hides construction choices so callers depend on a simple creation interface.
- - Factory separates “I need an object” from “which concrete class should be created?”
- - Factory moves creation decisions behind one method so callers depend on what they need, not every concrete constructor.
- The Builder pattern is a useful solution for when an object needs to be customized through a non-trivial, multi-step process and as an alternative to numerous parameters in the constructor.
- - Builder turns many constructor options into named, step-by-step object assembly.
- - Builder turns complex construction into explicit, readable steps before producing the final object.
- - Builder replaces overloaded constructor arguments with explicit, step-by-step object assembly.
- The Adapter pattern helps to bridge the gap between incompatible objects by transforming one object's interface into an interface expected by the other object, similar to physical adapters used in hardware.
- The Strategy pattern allows for a family of related algorithms to be defined, and for a decision of which one to use at runtime to be made based on user input, thereby avoiding
complex chains of conditions, and making it possible to switch strategies at call site in order to adapt to different situations, while related techniques such asDependency Injectionoffer added benefits. - The Observer pattern allows an object (the subject) to let others (the observers) know of any changes to its state by establishing a one-to-many dependency between them.
- The Visitor pattern lets you add operations to objects without modifying them, separating an algorithm from the
object structureon which it operates, and avoidingconditional blocksandtype checks. - The concept of anti-patterns, which can evolve from overused design patterns, as well as architectural patterns such as
MVC,MVVM,ESBandP2P, are related to patterns in software development. - With resources such as
Awesome Software and Architectural Design Patterns,Design Patterns for HumansandLearning JavaScript Design Patternsby Addy Osmani, as well asOODesign.comand theGoF Design Patterns Reference, anyone can deepen their understanding of Design Patterns.