8.11.2019

TypeScript паттерн проектирования стратегия / Короткие заметки

Источником материала является данная книга: Head First. Паттерны проектирования. Обновленное юбилейное издание

Все примеры из книги можно найти в гит репозитории Head-First-Design-Patterns

Реализация паттерна на TypeScript.
Реализация паттерна на Java.

Обычно в книгах по паттарнам проектирования все примеры приводятся на Java или C++, C#, или что-то вроде этого. А JavaScript программисту хочется видеть эти же примеры но на понятном ему языке. Поэтому я решил, что будет совсем не плохо если в процессе чтения книги я буду переписывать примеры реализации разных паттернов проектирования на понятный мне TypeScript и изучаемый мною Golang.

Коротко о паттерне


Существует не мало паттернов и все они классифицированы. Когда я начал задумываться, что бы я хотел сказать от себя о паттерне стратегия в голову пришло словосочетания "Стратегия выбора поведения". После этого до  меня сразу дошло по чему стратегия является поведенческим паттерном.

Как почувствовать, что нужно применить стратегию?


Если логика поведения становится сложной и при этом появляется много условий когда поведение должно изменяться то я задумываюсь о применении паттерна стратегия. На пример, существует контент (в интерфейсе) который нужно частично скрывать. Это может быть картинка разделенная на 4 части половина из которых скрыта. Если у нас один тип контента то не нужно не каких проверок на тип контента т.е. у нас нет условий когда логика скрытия контента меняется. Как только начинает увеличиваться число типов контента, и мы понимаем что нужно не только определять тип контента но и менять логику скрытия для каждого типа, можно применить паттерн стратегия.

Абстрактную идею понять сложнее чем конкретную реализацию, поэтому я переписал один из вариантов реализации паттерна из книги Head First. Паттерны проектирования. Обновленное юбилейное издание на TypeScript.

Реализация Паттерна Стратегия на TypeScript


ссылка на репозиторий с кодом: Паттерн Проектирования Стратегия.
См. Книгу, Первая глава, стр. 38 приложение SimUDuck

Основная идея заключается в том, что мы не реализуем метод fly внутри класса Duck. Мы создаем отдельный класс для каждого типа полета, а затем создаем различные типы уток и помещаем экземпляр типа полета в свойство типа утки.


interface FlyBehaviorI {
  fly: () => void;
}

class FlyWithWings implements FlyBehaviorI {
  public fly(): void {
    alert("I'm flying!!");
  }
}

interface QuackBehaviorI {
  quack: () => void;
}

class Quack implements QuackBehaviorI {
  public quack(): void {
    alert("Quack");
  }
}

class MuteQuack implements QuackBehaviorI {
  public quack(): void {
    alert("<< Silence >>");
  }
}

class FlyNoWay implements FlyBehaviorI {
  public fly(): void {
    alert("I can't fly.");
  }
}

class FlyRocketPowered implements FlyBehaviorI {
  public fly(): void {
    alert("I'm flying with a rocket!");
  }
}

interface DuckI {
  flyBehavior: FlyBehaviorI;
  quackBehavior: QuackBehaviorI;

  swim: () => void;
  display: () => void;
  performFly: () => void;
  performQuack: () => void;
  setFlyBehavior: (fb: FlyBehaviorI) => void;
  setFlyBehavior: (qb: QuackBehaviorI) => void;
}

abstract class Duck implements DuckI {
  abstract flyBehavior: FlyBehaviorI;
  abstract quackBehavior: QuackBehaviorI;

  public swim(): void {
    alert('Duck swim!');
  }

  abstract display(): void;

  public performFly(): void {
    this.flyBehavior.fly();
  }

  public performQuack(): void {
    this.quackBehavior.quack();
  }

  public setFlyBehavior(fb: FlyBehaviorI): void {
    this.flyBehavior = fb;
  }

  public setQuackBehavior(qb: QuackBehaviorI): void {
    this.quackBehavior = qb;
  }
}

class ModelDuck extends Duck {
  // first way to define instance props  flyBehavior: FlyBehaviorI = new FlyNoWay();
  quackBehavior: QuackBehaviorI = new MuteQuack();

  constructor() {
    super();

    // or second way to define instance props    // this.flyBehavior = new FlyNoWay();    // this.quackBehavior = new MuteQuack();  }

  public display(): void {
    alert("I'm a model duck");
  }
}

class MallardDuck extends Duck {
  // first way to define instance props  flyBehavior: FlyBehaviorI = new FlyWithWings();
  quackBehavior: QuackBehaviorI = new Quack();

  constructor() {
    super();

    // or second way to define instance props    // this.quackBehavior = new Quack();    // this.flyBehavior = new FlyNoWay();  }

  public display(): void {
    alert("I'm a real Mallard duck");
  }
}

const mallard: Duck = new MallardDuck();
mallard.performQuack(); // Quackmallard.performFly(); // I'm flying!!
const model: Duck = new ModelDuck();
model.performFly(); // I can't flymodel.setFlyBehavior(new FlyRocketPowered());
model.performFly(); // I'm flying with a rocket