Skip to content

组件协作模式

概念

定义

  • “组件协作” 模式通过 晚绑定,来实现 框架与应用程序 之间的松耦合,是二者协作式常用的模式。

要点

  • 关注 “框架与应用程序的划分”;
  • 要有「框架」与「应用」的区隔思维。

模版方法模式(TemplateMethod)

动机

  • 在软件建构过程中,对于某一项任务,它常常有 稳定 的整体操作结构,但各个子步骤却有很多 改变 的需求。
  • 如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或晚期实现需求?

定义

  • 父类定义算法骨架(稳定),而将算法的一些步骤(变化)延迟到子类中;
  • Template Method 使得子类可以不改变(复用)该算法结构的情况下,重定义(override 重写)该算法的某些特定步骤。

UML 类图

x

角色

  • 抽象类(Abstract Class):抽象模板类,负责给出一个算法的轮廓和骨架。

    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
    • 基本方法:是整个算法中的一个步骤,包含抽象方法/具体方法/钩子方法
  • 具体子类/具体实现(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法

代码

// 抽象类
abstract class AbstractClass {
  // 模板方法
  public TemplateMethod(): void {
    this.SpecificMethod();
    this.abstractMethod1();
    this.abstractMethod2();
  }

  // 具体方法
  public SpecificMethod(): void {
    console.log('抽象类中的具体方法被调用...');
  }

  // 抽象方法1
  public abstract abstractMethod1(): void;

  // 抽象方法2
  public abstract abstractMethod2(): void;
}

// 具体子类
class ConcreteClass extends AbstractClass {
  public abstractMethod1(): void {
    console.log('抽象方法1的实现被调用...');
  }

  public abstractMethod2(): void {
    console.log('抽象方法2的实现被调用...');
  }
}

export default class TemplateMethodPattern {
  public static main(args: string[]): void {
    const tm: AbstractClass = new ConcreteClass();
    tm.TemplateMethod();
  }
}

要点总结

  • Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
  • 除了可以灵活应对子步骤的变化外,“不要调用我,让我来调用你” 的反向控制结构是Template Method的典型应用。
  • 在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),但一般推荐将它们设置为protected方法。
  • 绝大多数软件框架中都有 Template Method 模式。
  • 设计模式必须基于一个稳定点,如果全不稳定就不存在任何适用的设计模式了;反之,如果全稳定就不需要设计模式了。
  • Template Method 意为“样板”。run()方法就是样板,细节定义为纯虚函数,交由子类(用户)去定义。

策略模式(Strategy)

动机

  • 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
  • 如何在运行过程是根据需要的改变对象的算法?将算法和对象本身解耦,从而避免上述问题?

模式定义

  • 定义一系列算法,把它们一个个封装起来,并使用它们相互替换(变化)。该模式使得算法可独立于使用它们的用户程序(稳定)而变化(扩展,子类化)。

UML 类图

  • 策略模式

x

  • 策略工厂模式

x

角色

  • 抽象策略类(Strategy):定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  • 具体策略类(ConcreteStrategy):实现了抽象策略定义的接口,提供具体的算法实现。
  • 环境类(Context):持有一个策略类的引用,最终给客户端调用。

代码

// 抽象策略类
interface Strategy {
  strategyMethod(): void; // 策略方法
}

// 具体策略类A
class ConcreteStrategyA implements Strategy {
  public strategyMethod(): void {
    console.log('具体策略A的策略方法被访问!');
  }
}

// 具体策略类B
class ConcreteStrategyB implements Strategy {
  public strategyMethod(): void {
    console.log('具体策略B的策略方法被访问!');
  }
}

// 环境类
class Context {
  private strategy: Strategy;

  public getStrategy(): Strategy {
    return this.strategy;
  }

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

  public strategyMethod(): void {
    this.strategy.strategyMethod();
  }
}

export default class StrategyPattern {
  public static main(args: string[]): void {
    const c: Context = new Context();
    let s: Strategy = new ConcreteStrategyA();
    c.setStrategy(s);
    c.strategyMethod();

    console.log('---------');
    s = new ConcreteStrategyB();
    c.setStrategy(s);
    c.strategyMethod();
  }
}

要点总结

  • Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在 运行时 方便的根据需要在各个算法之间进行切换。
  • Strategy模式提供了用判断语句以外的另一种选择, 消除条件判断语句 ,就是在解耦合;含有许多判断条件判断语句的代码通常需要Strategy模式
  • 如果Strategy对象没有实例变量,那么上下文可以共享一个Strategy对象,从而节省对象开销。

观察者模式(Observer)

动机

  • 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”:一个对象的(Subject)的状态发生改变,所有的依赖对象(Observer)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好的抵御变化。
  • 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系的松耦合。

定义

  • 定义对象间的一对多(变化)的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
  • 又称 “发布-订阅模式”;符合依赖倒置原则。

UML 类图

x

角色

  • 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  • 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  • 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  • 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

代码

// 抽象目标
abstract class Subject {
  protected observers: Array<Observer> = new Array<Observer>();

  // 增加观察者方法
  public add(observer: Observer): void {
    this.observers.push(observer);
  }

  // 删除观察者方法
  public remove(observer: Observer): void {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }

  public abstract notifyObserver(): void; // 通知观察者方法
}

// 具体目标
class ConcreteSubject extends Subject {
  public notifyObserver(): void {
    console.log('具体目标发生改变...');
    console.log('--------------');

    for (const obs of this.observers) {
      obs.response();
    }
  }
}

// 抽象观察者
interface Observer {
  response(): void; // 反应
}

// 具体观察者1
class ConcreteObserver1 implements Observer {
  public response(): void {
    console.log('具体观察者1作出反应!');
  }
}

// 具体观察者1
class ConcreteObserver2 implements Observer {
  public response(): void {
    console.log('具体观察者2作出反应!');
  }
}

export default class ObserverPattern {
  public static main(args: string[]): void {
    const subject: Subject = new ConcreteSubject();
    const obs1: Observer = new ConcreteObserver1();
    const obs2: Observer = new ConcreteObserver2();
    subject.add(obs1);
    subject.add(obs2);
    subject.notifyObserver();
  }
}

要点总结

  • 使用面向对象的抽象,Observer观察者模式可以使得我们独立地改变目标与观察者,从而使二者的关系达到松耦合。
  • 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
  • 观察者可以自己决定是否需要订阅通知,目标对象对此一无所知。