Design Patterns with Go and TypeScript

Design Patterns: Elements of Reusable Object-Oriented Software
Design Patterns: Elements of Reusable Object-Oriented Software

Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, this book introduced software design patterns. This book is a pioneer and influenced much content of design patterns out there. I think it is a must-read book.

Many content creators out there re-explained those theories or patterns. They tried to explain them in their way. I agree with that. Because the book, the source, is old. The book was released in 1994. All examples are written in C++. Not many kids understand the C++ syntaxes. But trying to re-explain the patterns maybe will cause or create another theory. I wouldn't do that here. So, I suggest you read the book.

In short, design patterns are common patterns in software design or architecture. This book name and classify them. Using those named patterns while discussing and designing with other developers can be helpful.

From the book, we know that they worked hard to write it. They wrote it a long time. The book is not to be read once. I think the most important section is Design Pattern Catalog. We will read or open that section again and again. The case study from the book, Designing a Document Editor, is also the best part. It showed us how those design patterns solved it. So, we can make good software. However, before we go any further, I suggest you have some Object Oriented Programming (OOP) knowledge first. Feel free to read OOP in Go or OOP in TypeScript.

Design patterns have been classified into three types; Creational, Structural, and Behavioral. This article provides all examples written in GoLang and TypeScript. The goal is that we can understand design patterns through examples or a simple way just from a single article.

I was getting help from ChatGPT while making these examples. About a week after GPT-4 was just released. ChatGPT understands Design Patterns, but I still guided her to produce simple examples until met my expectation. In some cases we did refactoring.

I still have some ideas to improve this article. I will put them here or update this article in the future to bring good content quality about design patterns.

In my opinion, knowledge about design patterns is optional. I discussed this with other seniors and most of them agreed. But SOLID is a must.

Creational Patterns

Creational design patterns abstract the instantiation process. They help make a system independent of how its objects are created, composed, and represented.

In short, patterns for object creation.

Abstract Factory

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

In the example below, we have an AbstractFactory interface with two methods; CreateProductA and CreateProductB. This interface could have many clients or implementations. Let's say ConcreteFactory1 and ConcreteFactory2. They have their details for CreateProductA and CreateProductB.

  classDiagram
  class AbstractFactory{
    <<interface>>
    CreateProductA() ProductA
    CreateProductB() ProductB
  }
  class ConcreteFactory1{
    CreateProductA() ProductA
    CreateProductB() ProductB
  }
  class ConcreteFactory2{
    CreateProductA() ProductA
    CreateProductB() ProductB
  }
  AbstractFactory <|.. ConcreteFactory1
  AbstractFactory <|.. ConcreteFactory2

Go

type ProductA interface {
    DoSomething()
}

type ProductB interface {
    DoSomethingElse()
}

type ConcreteProductA1 struct{}

func (p ConcreteProductA1) DoSomething() {
    fmt.Println("Doing something in ConcreteProductA1")
}

type ConcreteProductB1 struct{}

func (p ConcreteProductB1) DoSomethingElse() {
    fmt.Println("Doing something else in ConcreteProductB1")
}

type ConcreteProductA2 struct{}

func (p ConcreteProductA2) DoSomething() {
    fmt.Println("Doing something in ConcreteProductA2")
}

type ConcreteProductB2 struct{}

func (p ConcreteProductB2) DoSomethingElse() {
    fmt.Println("Doing something else in ConcreteProductB2")
}

type AbstractFactory interface {
    CreateProductA() ProductA
    CreateProductB() ProductB
}

type ConcreteFactory1 struct{}

func (c ConcreteFactory1) CreateProductA() ProductA {
    return ConcreteProductA1{}
}

func (c ConcreteFactory1) CreateProductB() ProductB {
    return ConcreteProductB1{}
}

type ConcreteFactory2 struct{}

func (c ConcreteFactory2) CreateProductA() ProductA {
    return ConcreteProductA2{}
}

func (c ConcreteFactory2) CreateProductB() ProductB {
    return ConcreteProductB2{}
}

func GetFactory(factoryType string) AbstractFactory {
    switch factoryType {
    case "Factory1":
        return ConcreteFactory1{}
    case "Factory2":
        return ConcreteFactory2{}
    default:
        return nil
    }
}    

TypeScript

interface ProductA {
  doSomething(): void;
}

interface ProductB {
  doSomethingElse(): void;
}

class ConcreteProductA1 implements ProductA {
  doSomething(): void {
    console.log("Doing something in ConcreteProductA1");
  }
}

class ConcreteProductB1 implements ProductB {
  doSomethingElse(): void {
    console.log("Doing something else in ConcreteProductB1");
  }
}

class ConcreteProductA2 implements ProductA {
  doSomething(): void {
    console.log("Doing something in ConcreteProductA2");
  }
}

class ConcreteProductB2 implements ProductB {
  doSomethingElse(): void {
    console.log("Doing something else in ConcreteProductB2");
  }
}

interface AbstractFactory {
  createProductA(): ProductA;
  createProductB(): ProductB;
}

class ConcreteFactory1 implements AbstractFactory {
  createProductA(): ProductA {
    return new ConcreteProductA1();
  }

  createProductB(): ProductB {
    return new ConcreteProductB1();
  }
}

class ConcreteFactory2 implements AbstractFactory {
  createProductA(): ProductA {
    return new ConcreteProductA2();
  }

  createProductB(): ProductB {
    return new ConcreteProductB2();
  }
}

function getFactory(factoryType: string): AbstractFactory | null {
  switch (factoryType) {
    case "Factory1":
      return new ConcreteFactory1();
    case "Factory2":
      return new ConcreteFactory2();
    default:
      return null;
  }
}

Builder

Separate the construction of a complex object from its representation so that the same construction process can create different representations.

You have an interface, let's say, Builder. It has multiple methods of building parts and a method to return a result.

  classDiagram
  class Builder{
    <<interface>>
    BuildPart1()
    BuildPart2()
    BuildPart3()
    GetResult() Product
  }
  class ConcreteBuilder1{
    BuildPart1()
    BuildPart2()
    BuildPart3()
    GetResult() Product
  }
  class ConcreteBuilder2{
    BuildPart1()
    BuildPart2()
    BuildPart3()
    GetResult() Product
  }
  Builder <|.. ConcreteBuilder1
  Builder <|.. ConcreteBuilder2

Go

type Builder interface {
    BuildPart1()
    BuildPart2()
    BuildPart3()
    GetResult() Product
}

type Product struct {
    parts []string
}

func (p *Product) AddPart(part string) {
    p.parts = append(p.parts, part)
}

type ConcreteBuilder1 struct {
    product Product
}

func (b *ConcreteBuilder1) BuildPart1() {
    b.product.AddPart("Part 1 of ConcreteBuilder1")
}

func (b *ConcreteBuilder1) BuildPart2() {
    b.product.AddPart("Part 2 of ConcreteBuilder1")
}

func (b *ConcreteBuilder1) BuildPart3() {
    b.product.AddPart("Part 3 of ConcreteBuilder1")
}

func (b *ConcreteBuilder1) GetResult() Product {
    return b.product
}

type ConcreteBuilder2 struct {
    product Product
}

func (b *ConcreteBuilder2) BuildPart1() {
    b.product.AddPart("Part 1 of ConcreteBuilder2")
}

func (b *ConcreteBuilder2) BuildPart2() {
    b.product.AddPart("Part 2 of ConcreteBuilder2")
}

func (b *ConcreteBuilder2) BuildPart3() {
    b.product.AddPart("Part 3 of ConcreteBuilder2")
}

func (b *ConcreteBuilder2) GetResult() Product {
    return b.product
}

type Director struct{}

func (d Director) Construct(builder Builder) Product {
    builder.BuildPart1()
    builder.BuildPart2()
    builder.BuildPart3()
    return builder.GetResult()
}  

TypeScript

class Product {
  private parts: string[];

  constructor() {
    this.parts = [];
  }

  addPart(part: string): void {
    this.parts.push(part);
  }

  getParts(): string[] {
    return this.parts;
  }
}

interface Builder {
  buildPart1(): void;
  buildPart2(): void;
  buildPart3(): void;
  getResult(): Product;
}

class ConcreteBuilder1 implements Builder {
  private product: Product;

  constructor() {
    this.product = new Product();
  }

  buildPart1(): void {
    this.product.addPart("Part 1 of ConcreteBuilder1");
  }

  buildPart2(): void {
    this.product.addPart("Part 2 of ConcreteBuilder1");
  }

  buildPart3(): void {
    this.product.addPart("Part 3 of ConcreteBuilder1");
  }

  getResult(): Product {
    return this.product;
  }
}

class ConcreteBuilder2 implements Builder {
  private product: Product;

  constructor() {
    this.product = new Product();
  }

  buildPart1(): void {
    this.product.addPart("Part 1 of ConcreteBuilder2");
  }

  buildPart2(): void {
    this.product.addPart("Part 2 of ConcreteBuilder2");
  }

  buildPart3(): void {
    this.product.addPart("Part 3 of ConcreteBuilder2");
  }

  getResult(): Product {
    return this.product;
  }
}

class Director {
  construct(builder: Builder): Product {
    builder.buildPart1();
    builder.buildPart2();
    builder.buildPart3();
    return builder.getResult();
  }
}  

Factory Method

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Almost similar to AbstractFactory. The difference is, AbstractFactory is an entire object for creation, while FactoryMethod is a just method.

  classDiagram
  class Creator{
    <<interface>>
    FactoryMethod() Product
  }
  class ConcreteCreator1{
    FactoryMethod() Product
  }
  class ConcreteCreator2{
    FactoryMethod() Product
  }
  Creator <|.. ConcreteCreator1
  Creator <|.. ConcreteCreator2

Go

type Creator interface {
    FactoryMethod() Product
}

type Product interface {
    Use()
}

type ConcreteProduct1 struct{}

func (p *ConcreteProduct1) Use() {
    fmt.Println("Using ConcreteProduct1")
}

type ConcreteProduct2 struct{}

func (p *ConcreteProduct2) Use() {
    fmt.Println("Using ConcreteProduct2")
}

type ConcreteCreator1 struct{}

func (c *ConcreteCreator1) FactoryMethod() Product {
    return &ConcreteProduct1{}
}

type ConcreteCreator2 struct{}

func (c *ConcreteCreator2) FactoryMethod() Product {
    return &ConcreteProduct2{}
}  

TypeScript

interface Product {
  use(): void;
}

class ConcreteProduct1 implements Product {
  use(): void {
    console.log("Using ConcreteProduct1");
  }
}

class ConcreteProduct2 implements Product {
  use(): void {
    console.log("Using ConcreteProduct2");
  }
}

interface Creator {
  factoryMethod(): Product;
}

class ConcreteCreator1 implements Creator {
  factoryMethod(): Product {
    return new ConcreteProduct1();
  }
}

class ConcreteCreator2 implements Creator {
  factoryMethod(): Product {
    return new ConcreteProduct2();
  }
}  

Prototype

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

The Prototype can clone itself.

  classDiagram
  class Prototype{
    <<interface>>
    Clone() Prototype
  }
  class ConcretePrototype1{
    property1 string
    property2 int
    Clone() Prototype
  }
  class ConcretePrototype2{
    property1 int
    property2 int
    Clone() Prototype
  }
  Prototype <|.. ConcretePrototype1
  Prototype <|.. ConcretePrototype2

Go

type Prototype interface {
    Clone() Prototype
}

type ConcretePrototype1 struct {
    property1 string
    property2 int
}

func (p *ConcretePrototype1) Clone() Prototype {
    return &ConcretePrototype1{p.property1, p.property2}
}

type ConcretePrototype2 struct {
    property1 int
    property2 int
}

func (p *ConcretePrototype2) Clone() Prototype {
    return &ConcretePrototype2{p.property1, p.property2}
}

type Client struct{}

func (c *Client) Operation(prototype Prototype) {
    clone := prototype.Clone()
    fmt.Printf("Cloned object: %+v\n", clone)
}

func main() {
    prototype1 := &ConcretePrototype1{"hello", 123}
    client := &Client{}
    client.Operation(prototype1)
  
    prototype2 := &ConcretePrototype2{456, 789}
    client.Operation(prototype2)
}  

TypeScript

interface Prototype {
  clone(): Prototype;
}

class ConcretePrototype1 implements Prototype {
  constructor(public property1: string, public property2: number) {}

  public clone(): Prototype {
    return new ConcretePrototype1(this.property1, this.property2);
  }
}

class ConcretePrototype2 implements Prototype {
  constructor(public property1: number, public property2: number) {}

  public clone(): Prototype {
    return new ConcretePrototype2(this.property1, this.property2);
  }
}

class Client {
  public static operation(prototype: Prototype): void {
    const clone = prototype.clone();
    console.log(`Cloned object: ${JSON.stringify(clone)}`);
  }
}

const prototype1 = new ConcretePrototype1("hello", 123);
Client.operation(prototype1);

const prototype2 = new ConcretePrototype2(456, 789);
Client.operation(prototype2);  

Singleton

Ensure a class only has one instance, and provide a global point of access to it.

The point of Singleton or these examples below is using one instance only for all its consumers.

Go

type Singleton struct {
    data string
}

var instance *Singleton
var once sync.Once

func GetInstance() *Singleton {
    once.Do(func() {
        instance = &Singleton{data: "Hello, World!"}
    })
    return instance
}

func main() {
    s1 := GetInstance()
    s2 := GetInstance()
    if s1 == s2 {
        fmt.Println("s1 and s2 are the same instance")
    } else {
        fmt.Println("s1 and s2 are different instances")
    }
    fmt.Printf("s1 data: %s\n", s1.data)
    fmt.Printf("s2 data: %s\n", s2.data)
}  

TypeScript

class Singleton {
  private static instance: Singleton;
  private data: string;

  private constructor() {
    this.data = "Hello, World!";
  }

  public static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }

  public getData(): string {
    return this.data;
  }
}

const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
if (s1 === s2) {
  console.log("s1 and s2 are the same instance");
} else {
  console.log("s1 and s2 are different instances");
}
console.log(`s1 data: ${s1.getData()}`);
console.log(`s2 data: ${s2.getData()}`);  

Structural Patterns

Structural patterns are concerned with how classes and objects are composed to form larger structures. Structural class patterns use inheritance to compose interfaces or implementations.

Adapter

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.

Let's say you have multiple adapters. Each adapter has different properties for its implementation. That's why we need a Target interface that has a Request method that has many implementations.

  classDiagram
  class Target{
    <<interface>>
    Request()
  }
  class Adapter{
    adaptee Adaptee
    Request()
  }
  class Adaptee{
    SpecificRequest()
  }
  Target <|.. Adapter
  Adapter --> Adaptee
  note for Adapter "adaptee.SpecificRequest()"

Go

type Target interface {
    Request()
}

type Adaptee struct{}

func (a *Adaptee) SpecificRequest() {
    fmt.Println("Adaptee's specific request")
}

type Adapter struct {
    adaptee *Adaptee
}

func (a *Adapter) Request() {
    fmt.Println("Adapter's request")
    a.adaptee.SpecificRequest()
}

func NewAdapter(adaptee *Adaptee) Target {
    return &Adapter{adaptee: adaptee}
}

func main() {
    adaptee := &Adaptee{}
    adapter := NewAdapter(adaptee)
  
    adapter.Request()
}  

TypeScript

interface Target {
  request(): void;
}

class Adaptee {
  public specificRequest(): void {
    console.log("Adaptee's specific request");
  }
}

class Adapter implements Target {
  constructor(private adaptee: Adaptee) {}

  public request(): void {
    console.log("Adapter's request");
    this.adaptee.specificRequest();
  }
}

const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);

adapter.request();  

Bridge

Decouple an abstraction from its implementation so that the two can vary independently.

Let's say you want to avoid permanent binding between an abstraction and its implementation. That is what Bridge is for.

  classDiagram
  class Abstraction{
    <<interface>>
    Operation()
  }
  class ConcreteAbstractionA{
    implementation Implementation
    Operation()
  }
  class ConcreteAbstractionB{
    implementation Implementation
    Operation()
  }
  class Implementation{
    <<interface>>
    OperationImplementation()
  }
  class ConcreteImplementationA{
    OperationImplementation()
  }
  class ConcreteImplementationB{
    OperationImplementation()
  }
  Abstraction o--> Implementation
  note for Abstraction "implementation.OperationImplementation()"
  Abstraction <|-- ConcreteAbstractionA
  Abstraction <|-- ConcreteAbstractionB
  Implementation <|-- ConcreteImplementationA
  Implementation <|-- ConcreteImplementationB

Go

type Abstraction interface {
    Operation()
}

type ConcreteAbstractionA struct {
    implementation Implementation
}

func (aa *ConcreteAbstractionA) Operation() {
    aa.implementation.OperationImplementation()
}

type ConcreteAbstractionB struct {
    implementation Implementation
}

func (ab *ConcreteAbstractionB) Operation() {
    ab.implementation.OperationImplementation()
}

type Implementation interface {
    OperationImplementation()
}

type ConcreteImplementationA struct{}

func (cia *ConcreteImplementationA) OperationImplementation() {
    fmt.Println("ConcreteImplementationA operation")
}

type ConcreteImplementationB struct{}

func (cib *ConcreteImplementationB) OperationImplementation() {
    fmt.Println("ConcreteImplementationB operation")
}

func main() {
    concreteImplementationA := &ConcreteImplementationA{}
    concreteImplementationB := &ConcreteImplementationB{}
  
    concreteAbstractionA := &ConcreteAbstractionA{implementation: concreteImplementationA}
    concreteAbstractionB := &ConcreteAbstractionB{implementation: concreteImplementationB}
  
    concreteAbstractionA.Operation()
    concreteAbstractionB.Operation()
}  

TypeScript

interface Abstraction {
  operation(): void;
}

class ConcreteAbstractionA implements Abstraction {
  constructor(private implementation: Implementation) {}

  public operation(): void {
    this.implementation.operationImplementation();
  }
}

class ConcreteAbstractionB implements Abstraction {
  constructor(private implementation: Implementation) {}

  public operation(): void {
    this.implementation.operationImplementation();
  }
}

interface Implementation {
  operationImplementation(): void;
}

class ConcreteImplementationA implements Implementation {
  public operationImplementation(): void {
    console.log("ConcreteImplementationA operation");
  }
}

class ConcreteImplementationB implements Implementation {
  public operationImplementation(): void {
    console.log("ConcreteImplementationB operation");
  }
}

const concreteImplementationA = new ConcreteImplementationA();
const concreteImplementationB = new ConcreteImplementationB();

const concreteAbstractionA = new ConcreteAbstractionA(concreteImplementationA);
const concreteAbstractionB = new ConcreteAbstractionB(concreteImplementationB);

concreteAbstractionA.operation();
concreteAbstractionB.operation();  

Composite

Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

Just like tree data structure. The parent leaf operates its children's operations.

  classDiagram
  class Component{
    <<interface>>
    Operation()
  }
  class Leaf{
    Operation()
  }
  class Composite{
    children []Component
    Add(Component)
    Remove(Component)
    Operation()
  }
  Component <|-- Leaf
  Composite o--> Component
  note for Composite "for child range children\nchild.Operation()"

Go

type Component interface {
    Operation()
}

type Leaf struct{}

func (leaf *Leaf) Operation() {
    fmt.Println("Leaf Operation")
}

type Composite struct {
    children []Component
}

func (composite *Composite) Add(child Component) {
    composite.children = append(composite.children, child)
}

func (composite *Composite) Remove(child Component) {
    for i, c := range composite.children {
        if c == child {
            composite.children = append(composite.children[:i], composite.children[i+1:]...)
            return
        }
    }
}

func (composite *Composite) Operation() {
    fmt.Println("Composite Operation")
    for _, child := range composite.children {
        child.Operation()
    }
}

func main() {
    root := &Composite{}
    root.Add(&Leaf{})
    branch := &Composite{}
    branch.Add(&Leaf{})
    branch.Add(&Leaf{})
    root.Add(branch)
    root.Operation()
}  

TypeScript

interface Component {
  operation(): void;
}

class Leaf implements Component {
  operation() {
    console.log("Leaf Operation");
  }
}

class Composite implements Component {
  private children: Component[] = [];

  add(child: Component) {
    this.children.push(child);
  }

  remove(child: Component) {
    const index = this.children.indexOf(child);
    if (index > -1) {
      this.children.splice(index, 1);
    }
  }

  operation() {
    console.log("Composite Operation");
    this.children.forEach(child => child.operation());
  }
}

const root: Composite = new Composite();
root.add(new Leaf());
const branch: Composite = new Composite();
branch.add(new Leaf());
branch.add(new Leaf());
root.add(branch);
root.operation();  

Decorator

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

There is a Decorator to wrap a component, just like a wrapper.

  classDiagram
  class Component{
    <<interface>>
    Operation() string
  }
  class ConcreteComponent{
    Operation() string
  }
  class Decorator{
    <<interface>>
    SetComponent(Component)
  }
  class ConcreteDecoratorA{
    component Component
    SetComponent(Component)
    Operation() string
  }
  class ConcreteDecoratorB{
    component Component
    SetComponent(Component)
    Operation() string
  }
  Component <|-- ConcreteComponent
  Decorator <|-- ConcreteDecoratorA
  Decorator <|-- ConcreteDecoratorB
  Decorator o--> Component
  note for Decorator "component.Operation()"

Go

type Component interface {
    Operation() string
}

type ConcreteComponent struct{}

func (cc *ConcreteComponent) Operation() string {
    return "ConcreteComponent"
}

type Decorator interface {
    Component
    SetComponent(Component)
}

type ConcreteDecoratorA struct {
    component Component
}

func (cda *ConcreteDecoratorA) SetComponent(component Component) {
    cda.component = component
}

func (cda *ConcreteDecoratorA) Operation() string {
    return fmt.Sprintf("ConcreteDecoratorA(%s)", cda.component.Operation())
}

type ConcreteDecoratorB struct {
    component Component
}

func (cdb *ConcreteDecoratorB) SetComponent(component Component) {
    cdb.component = component
}

func (cdb *ConcreteDecoratorB) Operation() string {
    return fmt.Sprintf("ConcreteDecoratorB(%s)", cdb.component.Operation())
}

func main() {
    component := &ConcreteComponent{}
    decoratorA := &ConcreteDecoratorA{}
    decoratorB := &ConcreteDecoratorB{}
  
    decoratorA.SetComponent(component)
    decoratorB.SetComponent(decoratorA)
  
    result := decoratorB.Operation()
    fmt.Println(result)
}  

TypeScript

interface Component {
  operation(): string;
}

class ConcreteComponent implements Component {
  operation() {
    return "ConcreteComponent";
  }
}

interface Decorator extends Component {
  setComponent(component: Component): void;
}

class ConcreteDecoratorA implements Decorator {
  private component: Component;

  setComponent(component: Component) {
    this.component = component;
  }

  operation() {
    return `ConcreteDecoratorA(${this.component.operation()})`;
  }
}

class ConcreteDecoratorB implements Decorator {
  private component: Component;

  setComponent(component: Component) {
      this.component = component;
  }

  operation() {
    return `ConcreteDecoratorB(${this.component.operation()})`;
  }
}

const component: Component = new ConcreteComponent();
const decoratorA: Decorator = new ConcreteDecoratorA();
const decoratorB: Decorator = new ConcreteDecoratorB();

decoratorA.setComponent(component);
decoratorB.setComponent(decoratorA);

const result: string = decoratorB.operation();
console.log(result);  

Facade

Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

A Facade depends on some objects or abstractions.

  classDiagram
  PaymentFacade ..> Account
  PaymentFacade ..> Wallet
  PaymentFacade ..> Security

Go

type PaymentFacade struct {
    acc      *Account
    wallet   *Wallet
    security *Security
}

func (p *PaymentFacade) MakePayment(amount float64) error {
    p.acc.checkBalance(amount)
    p.wallet.debit(amount)
    p.security.verify()
    fmt.Printf("Payment of $%.2f has been made\n", amount)
    return nil
}

type Account struct {
    balance float64
}

func (a *Account) checkBalance(amount float64) error {
    if a.balance < amount {
      return fmt.Errorf("not enough balance")
    }
    return nil
}

type Wallet struct {
    balance float64
}

func (w *Wallet) debit(amount float64) error {
    if w.balance < amount {
      return fmt.Errorf("not enough balance")
    }
    w.balance -= amount
    return nil
}

type Security struct{}

func (s *Security) verify() error {
    fmt.Println("Security verification complete")
    return nil
}

func main() {
    acc := &Account{1000.0}
    wallet := &Wallet{500.0}
    security := &Security{}
    payment := &PaymentFacade{acc, wallet, security}
  
    err := payment.MakePayment(750.0)
    if err != nil {
        fmt.Printf("Error: %s\n", err)
    }
}  

TypeScript

class PaymentFacade {
  private account: Account;
  private wallet: Wallet;
  private security: Security;

  constructor() {
    this.account = new Account(1000);
    this.wallet = new Wallet(500);
    this.security = new Security();
  }

  public makePayment(amount: number): void {
    try {
      this.account.checkBalance(amount);
      this.wallet.debit(amount);
      this.security.verify();
      console.log(`Payment of $${amount.toFixed(2)} has been made`);
    } catch (err) {
      console.error(`Error: ${err}`);
    }
  }
}

class Account {
  private balance: number;

  constructor(balance: number) {
    this.balance = balance;
  }

  public checkBalance(amount: number): void {
    if (this.balance < amount) {
      throw new Error('not enough balance');
    }
  }
}

class Wallet {
  private balance: number;

  constructor(balance: number) {
    this.balance = balance;
  }

  public debit(amount: number): void {
    if (this.balance < amount) {
      throw new Error('not enough balance');
    }
    this.balance -= amount;
  }
}

class Security {
  public verify(): void {
    console.log('Security verification complete');
  }
}

const payment = new PaymentFacade();
payment.makePayment(750.0);  

Flyweight

Use sharing to support large numbers of fine-grained objects efficiently.

A Flyweight could be effective when an application uses a large number of objects and doesn't depend on object identity because it uses shared flyweight objects.

  classDiagram
  class Flyweight{
    <<interface>>
    Operation()
  }
  class ConcreteFlyweight{
    state string
    Operation()
  }
  class FlyweightFactory{
    flyweights Flyweights
    GetFlyweight(key string) Flyweight
  }
  Flyweight <|-- ConcreteFlyweight
  FlyweightFactory --> Flyweight

Go

type Flyweight interface {
    Operation()
}

type ConcreteFlyweight struct {
    state string
}

func (f *ConcreteFlyweight) Operation() {
    fmt.Printf("ConcreteFlyweight: %s\n", f.state)
}

type FlyweightFactory struct {
    flyweights map[string]Flyweight
}

func (f *FlyweightFactory) GetFlyweight(key string) Flyweight {
    if flyweight, ok := f.flyweights[key]; ok {
        return flyweight
    }
    flyweight := &ConcreteFlyweight{state: key}
    f.flyweights[key] = flyweight
    return flyweight
}

func NewFlyweightFactory() *FlyweightFactory {
    return &FlyweightFactory{
        flyweights: make(map[string]Flyweight),
    }
}

func main() {
    factory := NewFlyweightFactory()
  
    flyweight1 := factory.GetFlyweight("key1")
    flyweight2 := factory.GetFlyweight("key2")
    flyweight3 := factory.GetFlyweight("key1")
  
    flyweight1.Operation()
    flyweight2.Operation()
    flyweight3.Operation()
  
    fmt.Println(len(factory.flyweights))
}  

TypeScript

interface Flyweight {
  operation(): void;
}

class ConcreteFlyweight implements Flyweight {
  private state: string;

  constructor(state: string) {
      this.state = state;
  }

  public operation(): void {
      console.log(`ConcreteFlyweight: ${this.state}`);
  }
}

class FlyweightFactory {
  private flyweights: { [key: string]: Flyweight } = {};

  public getFlyweight(key: string): Flyweight {
      if (key in this.flyweights) {
          return this.flyweights[key];
      }
      const flyweight = new ConcreteFlyweight(key);
      this.flyweights[key] = flyweight;
      return flyweight;
  }

  public getFlyweightCount(): number {
      return Object.keys(this.flyweights).length;
  }
}

const factory = new FlyweightFactory();

const flyweight1 = factory.getFlyweight("key1");
const flyweight2 = factory.getFlyweight("key2");
const flyweight3 = factory.getFlyweight("key1");

flyweight1.operation();
flyweight2.operation();
flyweight3.operation();

console.log(factory.getFlyweightCount());  

Proxy

Provide a surrogate or placeholder for another object to control access to it.

We need to pass a proxy before the real subject.

  classDiagram
  class Subject{
    <<interface>>
    Do() string
  }
  class RealSubject{
    Do() string
  }
  class Proxy{
    realSubject RealSubject
    Do() string
  }
  Subject <|-- RealSubject
  Proxy --> RealSubject

Go

type Subject interface {
    Do() string
}

type RealSubject struct{}

func (s *RealSubject) Do() string {
    return "RealSubject: doing something"
}

type Proxy struct {
    realSubject *RealSubject
}

func (p *Proxy) Do() string {
    if p.realSubject == nil {
        p.realSubject = &RealSubject{}
    }
    result := "Proxy: calling the real subject to execute a task\n"
    result += p.realSubject.Do()
    return result
}

func main() {
    var subject Subject = &Proxy{}
    result := subject.Do()
    fmt.Println(result)
}  

TypeScript

interface Subject {
  do(): string;
}

class RealSubject implements Subject {
  public do(): string {
    return "RealSubject: doing something";
  }
}

class _Proxy implements Subject {
  private realSubject: RealSubject | null = null;

  public do(): string {
    if (!this.realSubject) {
      this.realSubject = new RealSubject();
    }
    let result = "Proxy: calling the real subject to execute a task\n";
    result += this.realSubject.do();
    return result;
  }
}

const subject: Subject = new _Proxy();
const result = subject.do();
console.log(result);  

Behavioral Patterns

Behavioral patterns are concerned with algorithms and the assignment of responsibili- ties between objects. Behavioral patterns describe not just patterns of objects or classes but also the patterns of communication between them. These patterns characterize complex control flow that's difficult to follow at run-time. They shift your focus away from flow of control to let you concentratejust on the way objects are interconnected.

Chain of Responsibility

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

We can see from the example below that ConcreteHandler1 and ConcreteHandler2 are chained. The ConcreteHandler2 would handle the request if ConcreteHandler1 couldn't, and so on.

  classDiagram
  class Handler{
    <<interface>>
    HandleRequest(request int)
    SetNextHandler(handler Handler)
  }
  class ConcreteHandler1{
    nextHandler Handler
    HandleRequest(request int)
    SetNextHandler(handler Handler)
  }
  class ConcreteHandler2{
    nextHandler Handler
    HandleRequest(request int)
    SetNextHandler(handler Handler)
  }
  class Client{
    handler Handler
    MakeRequest(request int)
  }
  Handler <|-- ConcreteHandler1
  Handler <|-- ConcreteHandler2
  Client --> Handler
  Handler --> Handler : successor

Go

type Handler interface {
    HandleRequest(request int)
    SetNextHandler(handler Handler)
}

type ConcreteHandler1 struct {
    nextHandler Handler
}

func (c *ConcreteHandler1) HandleRequest(request int) {
    switch {
    case request >= 0 && request < 10:
        fmt.Printf("Request %d handled by ConcreteHandler1\n", request)
    case c.nextHandler != nil:
        c.nextHandler.HandleRequest(request)
    default:
        fmt.Println("Request cannot be handled by any handler")
    }
}

func (c *ConcreteHandler1) SetNextHandler(handler Handler) {
    c.nextHandler = handler
}

type ConcreteHandler2 struct {
    nextHandler Handler
}

func (c *ConcreteHandler2) HandleRequest(request int) {
    switch {
    case request >= 10 && request < 20:
        fmt.Printf("Request %d handled by ConcreteHandler2\n", request)
    case c.nextHandler != nil:
        c.nextHandler.HandleRequest(request)
    default:
        fmt.Println("Request cannot be handled by any handler")
    }
}

func (c *ConcreteHandler2) SetNextHandler(handler Handler) {
    c.nextHandler = handler
}

type Client struct {
    handler Handler
}

func (c *Client) MakeRequest(request int) {
    c.handler.HandleRequest(request)
}

func main() {
    handler1 := &ConcreteHandler1{}
    handler2 := &ConcreteHandler2{}
  
    handler1.SetNextHandler(handler2)
  
    client := &Client{handler: handler1}
  
    client.MakeRequest(5)
    client.MakeRequest(15)
    client.MakeRequest(25)
}  

TypeScript

interface Handler {
  handleRequest(request: number): void;
  setNextHandler(handler: Handler): void;
}

class ConcreteHandler1 implements Handler {
  private nextHandler: Handler;

  public handleRequest(request: number): void {
    if (request >= 0 && request < 10) {
      console.log(`Request ${request} handled by ConcreteHandler1`);
    } else if (this.nextHandler != null) {
      this.nextHandler.handleRequest(request);
    } else {
      console.log("Request cannot be handled by any handler");
    }
  }

  public setNextHandler(handler: Handler): void {
    this.nextHandler = handler;
  }
}

class ConcreteHandler2 implements Handler {
  private nextHandler: Handler;

  public handleRequest(request: number): void {
    if (request >= 10 && request < 20) {
      console.log(`Request ${request} handled by ConcreteHandler2`);
    } else if (this.nextHandler != null) {
      this.nextHandler.handleRequest(request);
    } else {
      console.log("Request cannot be handled by any handler");
    }
  }

  public setNextHandler(handler: Handler): void {
    this.nextHandler = handler;
  }
}

class Client {
  private handler: Handler;

  constructor(handler: Handler) {
    this.handler = handler;
  }

  public makeRequest(request: number): void {
    this.handler.handleRequest(request);
  }
}

const handler1: Handler = new ConcreteHandler1();
const handler2: Handler = new ConcreteHandler2();

handler1.setNextHandler(handler2);

const client: Client = new Client(handler1);

client.makeRequest(5);
client.makeRequest(15);
client.makeRequest(25);  

Command

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

  classDiagram
  class Command{
    <<interface>>
    Execute()
  }
  class ConcreteCommand{
    message string
    Execute()
  }
  class Invoker{
    command Command
    SetCommand(Command)
    ExecuteCommand()
  }
  Command <|-- ConcreteCommand
  Command o--> Invoker

Go

type Command interface {
    Execute()
}

type ConcreteCommand struct {
    message string
}

func (c *ConcreteCommand) Execute() {
    fmt.Println(c.message)
}

type Invoker struct {
    command Command
}

func (i *Invoker) SetCommand(c Command) {
    i.command = c
}

func (i *Invoker) ExecuteCommand() {
    i.command.Execute()
}

func main() {
    concreteCommand := &ConcreteCommand{message: "Hello World!"}
    invoker := &Invoker{}
    invoker.SetCommand(concreteCommand)
    invoker.ExecuteCommand()
}  

TypeScript

interface Command {
  execute(): void;
}

class ConcreteCommand implements Command {
  constructor(private message: string) {}

  execute(): void {
    console.log(this.message);
  }
}

class Invoker {
  private command: Command;

  setCommand(c: Command) {
    this.command = c;
  }

  executeCommand() {
    this.command.execute();
  }
}

const concreteCommand = new ConcreteCommand('Hello World!');
const invoker = new Invoker();
invoker.setCommand(concreteCommand);
invoker.executeCommand();  

Interpreter

Given a language, define a represention for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

The example below is about a calculator with basic calculations. I think that was a good example of an Interpreter.

  classDiagram
  class Expression{
    <<interface>>
    Interpret() int
  }
  class Number{
    value int
    Interpret() int
  }
  class Add{
    left Expression
    right Expression
    Interpret() int
  }
  class Subtract{
    left Expression
    right Expression
    Interpret() int
  }
  class Multiply{
    left Expression
    right Expression
    Interpret() int
  }
  class Divide{
    left Expression
    right Expression
    Interpret() int
  }
  Expression <|-- Number
  Expression <|-- Add
  Expression <|-- Subtract
  Expression <|-- Multiply
  Expression <|-- Divide

Go

type Expression interface {
    Interpret() int
}

type Number struct {
    value int
}

func (n *Number) Interpret() int {
    return n.value
}

type Add struct {
    left  Expression
    right Expression
}

func (a *Add) Interpret() int {
    return a.left.Interpret() + a.right.Interpret()
}

type Subtract struct {
    left  Expression
    right Expression
}

func (s *Subtract) Interpret() int {
    return s.left.Interpret() - s.right.Interpret()
}

type Multiply struct {
    left  Expression
    right Expression
}

func (m *Multiply) Interpret() int {
    return m.left.Interpret() * m.right.Interpret()
}

type Divide struct {
    left  Expression
    right Expression
}

func (d *Divide) Interpret() int {
    return d.left.Interpret() / d.right.Interpret()
}

func main() {
    expression := &Add{
        left: &Number{value: 10},
        right: &Multiply{
            left:  &Number{value: 2},
            right: &Subtract{left: &Number{value: 5}, right: &Number{value: 3}},
        },
    }
    result := expression.Interpret()
    fmt.Printf("Result: %d\n", result)
}  

TypeScript

interface Expression {
  interpret(): number;
}

class Value implements Expression {
  value: number;
  
  constructor(value: number) {
    this.value = value;
  }

  interpret(): number {
    return this.value;
  }
}

class Add implements Expression {
  left: Expression;
  right: Expression;

  constructor(left: Expression, right: Expression) {
    this.left = left;
    this.right = right;
  }

  interpret(): number {
    return this.left.interpret() + this.right.interpret();
  }
}

class Subtract implements Expression {
  left: Expression;
  right: Expression;

  constructor(left: Expression, right: Expression) {
    this.left = left;
    this.right = right;
  }

  interpret(): number {
    return this.left.interpret() - this.right.interpret();
  }
}

class Multiply implements Expression {
  left: Expression;
  right: Expression;

  constructor(left: Expression, right: Expression) {
    this.left = left;
    this.right = right;
  }

  interpret(): number {
    return this.left.interpret() * this.right.interpret();
  }
}

class Divide implements Expression {
  left: Expression;
  right: Expression;

  constructor(left: Expression, right: Expression) {
    this.left = left;
    this.right = right;
  }

  interpret(): number {
    return this.left.interpret() / this.right.interpret();
  }
}

const expression: Expression = new Add(
  new Value(10),
  new Multiply(
    new Value(2),
    new Subtract(new Value(5), new Value(3)),
  ),
);

const result: number = expression.interpret();
console.log(`Result: ${result}`);  

Iterator

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

It is an abstraction to iterate objects.

  classDiagram
  class Iterator{
    <<interface>>
    HasNext() bool
    Next() any
  }
  class ConcreteAggregate{
    items []any
    CreateIterator() Iterator
  }
  class ConcreteIterator{
    aggregate ConcreteAggregate
    index int
    HasNext() bool
    Next() any
  }
  Iterator <|-- ConcreteIterator
  ConcreteAggregate <-- ConcreteIterator

Go

type Iterator interface {
    HasNext() bool
    Next() interface{}
}

type ConcreteIterator struct {
    aggregate *ConcreteAggregate
    index     int
}

func (i *ConcreteIterator) HasNext() bool {
    return i.index < len(i.aggregate.items)
}

func (i *ConcreteIterator) Next() interface{} {
    if !i.HasNext() {
        return nil
    }
    item := i.aggregate.items[i.index]
    i.index++
    return item
}

type ConcreteAggregate struct {
    items []interface{}
}

func (a *ConcreteAggregate) CreateIterator() Iterator {
    return &ConcreteIterator{
        aggregate: a,
        index:     0,
    }
}

func main() {
    aggregate := &ConcreteAggregate{
        items: []interface{}{"A", "B", "C", "D", "E"},
    }
    iterator := aggregate.CreateIterator()
    for iterator.HasNext() {
        item := iterator.Next()
        fmt.Printf("%s ", item)
    }
}  

TypeScript

interface Iterator<T> {
  hasNext(): boolean;
  next(): T;
}

interface Aggregate<T> {
  createIterator(): Iterator<T>;
}

class ConcreteAggregate implements Aggregate<number> {
  private numbers: number[];

  constructor(numbers: number[]) {
    this.numbers = numbers;
  }

  createIterator(): Iterator<number> {
    return new ConcreteIterator(this);
  }

  getNumbers(): number[] {
    return this.numbers;
  }
}

class ConcreteIterator implements Iterator<number> {
  private collection: ConcreteAggregate;
  private index: number;

  constructor(collection: ConcreteAggregate) {
    this.collection = collection;
    this.index = 0;
  }

  hasNext(): boolean {
    return this.index < this.collection.getNumbers().length;
  }

  next(): number {
    if (!this.hasNext()) {
      return null;
    }
    const num = this.collection.getNumbers()[this.index];
    this.index++;
    return num;
  }
}

const concreteAggregate = new ConcreteAggregate([1, 2, 3, 4, 5]);
const iterator = concreteAggregate.createIterator();
while (iterator.hasNext()) {
  const num = iterator.next();
  console.log(num);
}  

Mediator

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

A Mediator mediates object interaction.

  classDiagram
  class Mediator{
    <<interface>>
    SendMessage(sender Colleague, message string)
  }
  class ConcreteMediator{
    colleague1 Colleague
    colleague2 Colleague
    SendMessage(sender Colleague, message string)
  }
  class Colleague{
    <<interface>>
    SendMessage(message string)
    Notify(message string)
  }
  class ConcreteColleague1{
    mediator Mediator
    SendMessage(message string)
    Notify(message string)
  }
  class ConcreteColleague2{
    mediator Mediator
    SendMessage(message string)
    Notify(message string)
  }
  Colleague <|-- ConcreteColleague1
  Colleague <|-- ConcreteColleague2
  Mediator <|-- ConcreteMediator
  Mediator <-- Colleague
  ConcreteMediator --> ConcreteColleague1
  ConcreteMediator --> ConcreteColleague2

Go

type Mediator interface {
    SendMessage(sender Colleague, message string)
}

type ConcreteMediator struct {
    colleague1 Colleague
    colleague2 Colleague
}

func (m *ConcreteMediator) SendMessage(sender Colleague, message string) {
    if sender == m.colleague1 {
        m.colleague2.Notify(message)
    } else {
        m.colleague1.Notify(message)
    }
}

type Colleague interface {
    SendMessage(message string)
    Notify(message string)
}

type ConcreteColleague1 struct {
    mediator Mediator
}

func (c *ConcreteColleague1) SendMessage(message string) {
    c.mediator.SendMessage(c, message)
}

func (c *ConcreteColleague1) Notify(message string) {
    fmt.Printf("ConcreteColleague1 received message: %s\n", message)
}

type ConcreteColleague2 struct {
    mediator Mediator
}

func (c *ConcreteColleague2) SendMessage(message string) {
    c.mediator.SendMessage(c, message)
}

func (c *ConcreteColleague2) Notify(message string) {
    fmt.Printf("ConcreteColleague2 received message: %s\n", message)
}

func main() {
    mediator := &ConcreteMediator{}
  
    colleague1 := &ConcreteColleague1{mediator: mediator}
    colleague2 := &ConcreteColleague2{mediator: mediator}
  
    mediator.colleague1 = colleague1
    mediator.colleague2 = colleague2
  
    colleague1.SendMessage("Hello, colleague2!")
    colleague2.SendMessage("Hi there, colleague1!")
}  

TypeScript

interface Mediator {
  sendMessage(sender: Colleague, message: string): void;
}

class ConcreteMediator implements Mediator {
  private colleague1!: Colleague;
  private colleague2!: Colleague;

  setColleague1(colleague: Colleague): void {
    this.colleague1 = colleague;
  }

  setColleague2(colleague: Colleague): void {
    this.colleague2 = colleague;
  }

  sendMessage(sender: Colleague, message: string): void {
    if (sender === this.colleague1) {
      this.colleague2.notify(message);
    } else {
      this.colleague1.notify(message);
    }
  }
}

interface Colleague {
  sendMessage(message: string): void;
  notify(message: string): void;
}

class ConcreteColleague1 implements Colleague {
  private mediator!: Mediator;

  setMediator(mediator: Mediator): void {
    this.mediator = mediator;
  }

  sendMessage(message: string): void {
    this.mediator.sendMessage(this, message);
  }

  notify(message: string): void {
    console.log(`ConcreteColleague1 received message: ${message}`);
  }
}

class ConcreteColleague2 implements Colleague {
  private mediator!: Mediator;

  setMediator(mediator: Mediator): void {
    this.mediator = mediator;
  }

  sendMessage(message: string): void {
    this.mediator.sendMessage(this, message);
  }

  notify(message: string): void {
    console.log(`ConcreteColleague2 received message: ${message}`);
  }
}

const mediator: Mediator = new ConcreteMediator();
const colleague1: Colleague = new ConcreteColleague1();
const colleague2: Colleague = new ConcreteColleague2();

mediator.setColleague1(colleague1);
mediator.setColleague2(colleague2);

colleague1.setMediator(mediator);
colleague2.setMediator(mediator);

colleague1.sendMessage("Hello, colleague2!");
colleague2.sendMessage("Hi there, colleague1!");  

Memento

Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.

In the example below, the Memento has one property, Text, to store the internal state of the Editor. So, the Caretaker could restore the state of the Editor later.

  classDiagram
  class Editor{
    text string
    Write(text string)
    Read() string
  }
  class Memento{
    text string
    Text() string
  }
  class Caretaker{
    memento Memento
    Save(Editor)
    Restor(Editor)
  }
  Memento <--o Caretaker
  Editor ..> Memento

Go

type Editor struct {
    text string
}

func (e *Editor) Write(text string) {
    e.text += text
}

func (e *Editor) Read() string {
    return e.text
}

type Memento struct {
    text string
}

func (m *Memento) Text() string {
    return m.text
}

type Caretaker struct {
    memento *Memento
}

func (c *Caretaker) Save(editor *Editor) {
    c.memento = &Memento{text: editor.Read()}
}

func (c *Caretaker) Restore(editor *Editor) {
    editor.Write(c.memento.Text())
}

func main() {
    editor := &Editor{}
    caretaker := &Caretaker{}
  
    editor.Write("Hello, ")
    caretaker.Save(editor)
  
    editor.Write("world!")
    fmt.Println(editor.Read())
  
    caretaker.Restore(editor)
    fmt.Println(editor.Read())
}  

TypeScript

class Editor {
  private text: string = '';

  write(text: string) {
    this.text += text;
  }

  read(): string {
    return this.text;
  }
}

class Memento {
  constructor(private text: string) {}

  getText(): string {
    return this.text;
  }
}

class Caretaker {
  private memento: Memento | null = null;

  save(editor: Editor) {
    this.memento = new Memento(editor.read());
  }

  restore(editor: Editor) {
    if (this.memento) {
      editor.write(this.memento.getText());
    }
  }
}

const editor = new Editor();
const caretaker = new Caretaker();

editor.write('Hello, ');
caretaker.save(editor);

editor.write('world!');
console.log(editor.read());

caretaker.restore(editor);
console.log(editor.read());  

Observer

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

You can see that MessagePublisher has many observers. They will be updated on Notify().

  classDiagram
  class Subject{
    <<interface>>
    Attach(Observer)
    Detach(Observer)
    Notify(message string)
  }
  class MessagePublisher{
    observers []Observer
    Attach(Observer)
    Detach(Observer)
    Notify(message string)
  }
  class Observer{
    <<interface>>
    Update(message string)
  }
  class MessageSubscriber{
    name string
    Update(message string)
  }
  Subject <|-- MessagePublisher
  Observer <|-- MessageSubscriber
  Subject o--> Observer
  note for Subject "for observer range observers\nobserver.Update()"

Go

type Subject interface {
    Attach(observer Observer)
    Detach(observer Observer)
    Notify(message string)
}

type Observer interface {
    Update(message string)
}

type MessagePublisher struct {
    observers []Observer
}

func (mp *MessagePublisher) Attach(observer Observer) {
    mp.observers = append(mp.observers, observer)
}

func (mp *MessagePublisher) Detach(observer Observer) {
    for i, obs := range mp.observers {
        if obs == observer {
            mp.observers = append(mp.observers[:i], mp.observers[i+1:]...)
            break
        }
    }
}

func (mp *MessagePublisher) Notify(message string) {
    for _, observer := range mp.observers {
        observer.Update(message)
    }
}

type MessageSubscriber struct {
    name string
}

func (ms *MessageSubscriber) Update(message string) {
    fmt.Printf("%s received message: %s\n", ms.name, message)
}

func main() {
    publisher := &MessagePublisher{}
  
    subscriber1 := &MessageSubscriber{name: "Subscriber 1"}
    subscriber2 := &MessageSubscriber{name: "Subscriber 2"}
  
    publisher.Attach(subscriber1)
    publisher.Attach(subscriber2)
  
    publisher.Notify("Hello, world!")
  
    publisher.Detach(subscriber2)
  
    publisher.Notify("How are you doing?")
}  

TypeScript

interface Subject {
  attach(observer: Observer): void;
  detach(observer: Observer): void;
  notify(message: string): void;
}

interface Observer {
  update(message: string): void;
}

class MessagePublisher implements Subject {
  private observers: Observer[] = [];

  attach(observer: Observer) {
    this.observers.push(observer);
  }

  detach(observer: Observer) {
    const index = this.observers.indexOf(observer);
    if (index !== -1) {
      this.observers.splice(index, 1);
    }
  }

  notify(message: string) {
    this.observers.forEach(observer => observer.update(message));
  }
}

class MessageSubscriber implements Observer {
  constructor(private name: string) {}

  update(message: string) {
    console.log(`${this.name} received message: ${message}`);
  }
}

const publisher = new MessagePublisher();

const subscriber1 = new MessageSubscriber('Subscriber 1');
const subscriber2 = new MessageSubscriber('Subscriber 2');

publisher.attach(subscriber1);
publisher.attach(subscriber2);

publisher.notify('Hello, world!');

publisher.detach(subscriber2);

publisher.notify('How are you doing?');  

State

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

  classDiagram
  class State{
    <<interface>>
    Handle()
  }
  class ConcreteStateA{
    Handle()
  }
  class ConcreteStateB{
    Handle()
  }
  class Context{
    state State
    SetState(State)
    Request()
  }
  State <|-- ConcreteStateA
  State <|-- ConcreteStateB
  Context o--> State

Go

type State interface {
    Handle()
}

type ConcreteStateA struct{}

func (s *ConcreteStateA) Handle() {
    fmt.Println("Handle request with ConcreteStateA")
}

type ConcreteStateB struct{}

func (s *ConcreteStateB) Handle() {
    fmt.Println("Handle request with ConcreteStateB")
}

type Context struct {
    state State
}

func (c *Context) SetState(state State) {
    c.state = state
}

func (c *Context) Request() {
    c.state.Handle()
}

func main() {
    context := &Context{}
    stateA := &ConcreteStateA{}
    stateB := &ConcreteStateB{}
  
    context.SetState(stateA)
    context.Request()
  
    context.SetState(stateB)
    context.Request()
}  

TypeScript

interface State {
  handle(): void;
}

class ConcreteStateA implements State {
  handle(): void {
    console.log("Handle request with ConcreteStateA");
  }
}

class ConcreteStateB implements State {
  handle(): void {
    console.log("Handle request with ConcreteStateB");
  }
}

class Context {
  private state: State;

  public setState(state: State) {
    this.state = state;
  }

  public request() {
    this.state.handle();
  }
}

const context = new Context();
const stateA = new ConcreteStateA();
const stateB = new ConcreteStateB();

context.setState(stateA);
context.request();

context.setState(stateB);
context.request();  

Strategy

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Let's say you want to execute something and have several algorithms. That is when you need a Strategy pattern.

  classDiagram
  class Strategy{
    <<interface>>
    Execute()
  }
  class ConcreteStrategyA{
    Execute()
  }
  class ConcreteStrategyB{
    Execute()
  }
  class Context{
    strategy Strategy
    SetStrategy(strategy Strategy)
    ExecuteStrategy()
  }
  Strategy <|-- ConcreteStrategyA
  Strategy <|-- ConcreteStrategyB
  Context o--> Strategy

Go

type Strategy interface {
    Execute()
}

type ConcreteStrategyA struct{}

func (s *ConcreteStrategyA) Execute() {
    fmt.Println("Execute strategy A")
}

type ConcreteStrategyB struct{}

func (s *ConcreteStrategyB) Execute() {
    fmt.Println("Execute strategy B")
}

type Context struct {
    strategy Strategy
}

func (c *Context) SetStrategy(strategy Strategy) {
    c.strategy = strategy
}

func (c *Context) ExecuteStrategy() {
    c.strategy.Execute()
}

func main() {
    context := &Context{}
    strategyA := &ConcreteStrategyA{}
    strategyB := &ConcreteStrategyB{}
  
    context.SetStrategy(strategyA)
    context.ExecuteStrategy()
  
    context.SetStrategy(strategyB)
    context.ExecuteStrategy()
}  

TypeScript

interface Strategy {
  execute(): void;
}

class ConcreteStrategyA implements Strategy {
  execute(): void {
    console.log("Execute strategy A");
  }
}

class ConcreteStrategyB implements Strategy {
  execute(): void {
    console.log("Execute strategy B");
  }
}

class Context {
  private strategy: Strategy;

  public setStrategy(strategy: Strategy) {
    this.strategy = strategy;
  }

  public executeStrategy() {
    this.strategy.execute();
  }
}

const context = new Context();
const strategyA = new ConcreteStrategyA();
const strategyB = new ConcreteStrategyB();

context.setStrategy(strategyA);
context.executeStrategy();

context.setStrategy(strategyB);
context.executeStrategy();  

Template Method

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

  classDiagram
  class AbstractClass{
    <<interface>>
    TemplateMethod()
    PrimitiveOperation1()
    PrimitiveOperation2()
  }
  class ConcreteClass{
    TemplateMethod()
    PrimitiveOperation1()
    PrimitiveOperation2()
  }
  AbstractClass <|-- ConcreteClass
  note for AbstractClass "..\nPrimitiveOperation1()\nPrimitiveOperation2()\n.."

Go

type AbstractClass interface {
    TemplateMethod()
    PrimitiveOperation1()
    PrimitiveOperation2()
}

type ConcreteClass struct{}

func (c *ConcreteClass) TemplateMethod() {
    fmt.Println("ConcreteClass: template method starts")
    c.PrimitiveOperation1()
    c.PrimitiveOperation2()
    fmt.Println("ConcreteClass: template method ends")
}

func (c *ConcreteClass) PrimitiveOperation1() {
    fmt.Println("ConcreteClass: primitive operation 1")
}

func (c *ConcreteClass) PrimitiveOperation2() {
    fmt.Println("ConcreteClass: primitive operation 2")
}

func main() {
    concrete := &ConcreteClass{}
    concrete.TemplateMethod()
}  

TypeScript

abstract class AbstractClass {
  public TemplateMethod(): void {
    console.log("AbstractClass: template method starts");
    this.PrimitiveOperation1();
    this.PrimitiveOperation2();
    console.log("AbstractClass: template method ends");
  }

  protected abstract PrimitiveOperation1(): void;
  protected abstract PrimitiveOperation2(): void;
}

class ConcreteClass extends AbstractClass {
  protected PrimitiveOperation1(): void {
    console.log("ConcreteClass: primitive operation 1");
  }

  protected PrimitiveOperation2(): void {
    console.log("ConcreteClass: primitive operation 2");
  }
}

const concrete = new ConcreteClass();
concrete.TemplateMethod();  

Visitor

Represent an operation to be performed on the elements of an object structure.Visitor lets you define a new operation without changing the classes of the elements on which it operates.

In the example below, ConcreteElementA and ConcreteElementB accept a Visitor. They called visitors to visit themselves. Then the Visitor does some stuff and operates the element.

  classDiagram
  class Visitor{
    <<interface>>
    VisitConcreteElementA(ConcreteElementA)
    VisitConcreteElementB(ConcreteElementB)
  }
  class Element{
    <<interface>>
    Accept(Visitor)
  }
  class ConcreteElementA{
    Accept(Visitor)
    OperationA()
  }
  class ConcreteElementB{
    Accept(Visitor)
    OperationB()
  }
  class ConcreteVisitor1{
    VisitConcreteElementA(ConcreteElementA)
    VisitConcreteElementB(ConcreteElementB)
  }
  class ConcreteVisitor2{
    VisitConcreteElementA(ConcreteElementA)
    VisitConcreteElementB(ConcreteElementB)
  }
  Visitor <|-- ConcreteVisitor1
  Visitor <|-- ConcreteVisitor2
  Element <|-- ConcreteElementA
  Element <|-- ConcreteElementB

Go

type Visitor interface {
    VisitConcreteElementA(element *ConcreteElementA)
    VisitConcreteElementB(element *ConcreteElementB)
}

type Element interface {
    Accept(visitor Visitor)
}

type ConcreteElementA struct{}

func (e *ConcreteElementA) Accept(visitor Visitor) {
    visitor.VisitConcreteElementA(e)
}

func (e *ConcreteElementA) OperationA() {
    fmt.Println("ConcreteElementA: operation A")
}

type ConcreteElementB struct{}

func (e *ConcreteElementB) Accept(visitor Visitor) {
    visitor.VisitConcreteElementB(e)
}

func (e *ConcreteElementB) OperationB() {
    fmt.Println("ConcreteElementB: operation B")
}

type ConcreteVisitor1 struct{}

func (v *ConcreteVisitor1) VisitConcreteElementA(element *ConcreteElementA) {
    fmt.Println("ConcreteVisitor1: visiting ConcreteElementA")
    element.OperationA()
}

func (v *ConcreteVisitor1) VisitConcreteElementB(element *ConcreteElementB) {
    fmt.Println("ConcreteVisitor1: visiting ConcreteElementB")
    element.OperationB()
}

type ConcreteVisitor2 struct{}

func (v *ConcreteVisitor2) VisitConcreteElementA(element *ConcreteElementA) {
    fmt.Println("ConcreteVisitor2: visiting ConcreteElementA")
    element.OperationA()
}

func (v *ConcreteVisitor2) VisitConcreteElementB(element *ConcreteElementB) {
    fmt.Println("ConcreteVisitor2: visiting ConcreteElementB")
    element.OperationB()
}

func main() {
    elements := []Element{&ConcreteElementA{}, &ConcreteElementB{}}
  
    visitor1 := &ConcreteVisitor1{}
    visitor2 := &ConcreteVisitor2{}
  
    for _, element := range elements {
        element.Accept(visitor1)
        element.Accept(visitor2)
    }
}  

TypeScript

interface Visitor {
  visitConcreteElementA(element: ConcreteElementA): void;
  visitConcreteElementB(element: ConcreteElementB): void;
}

interface Element {
  accept(visitor: Visitor): void;
}

class ConcreteElementA implements Element {
  accept(visitor: Visitor): void {
    visitor.visitConcreteElementA(this);
  }

  operationA(): void {
    console.log("ConcreteElementA: operation A");
  }
}

class ConcreteElementB implements Element {
  accept(visitor: Visitor): void {
    visitor.visitConcreteElementB(this);
  }

  operationB(): void {
    console.log("ConcreteElementB: operation B");
  }
}

class ConcreteVisitor1 implements Visitor {
  visitConcreteElementA(element: ConcreteElementA): void {
    console.log("ConcreteVisitor1: visiting ConcreteElementA");
    element.operationA();
  }

  visitConcreteElementB(element: ConcreteElementB): void {
    console.log("ConcreteVisitor1: visiting ConcreteElementB");
    element.operationB();
  }
}

class ConcreteVisitor2 implements Visitor {
  visitConcreteElementA(element: ConcreteElementA): void {
    console.log("ConcreteVisitor2: visiting ConcreteElementA");
    element.operationA();
  }

  visitConcreteElementB(element: ConcreteElementB): void {
    console.log("ConcreteVisitor2: visiting ConcreteElementB");
    element.operationB();
  }
}

const elements: Element[] = [new ConcreteElementA(), new ConcreteElementB()];

const visitor1: Visitor = new ConcreteVisitor1();
const visitor2: Visitor = new ConcreteVisitor2();

for (const element of elements) {
  element.accept(visitor1);
  element.accept(visitor2);
}  

Related Articles

Comments