Skip to content

接口隔离模式

概念

定义

  • 在组建构建过程中,某些接口之间之间的依赖常常会带来很多问题、甚至根本无法实现。

  • 采用添加一层间接(稳定)接口,来隔离本来相互紧密关联的接口是一种常见的解决方案

外观模式(Facade)

动机

  • 当组件的客户和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战。
  • 如何简化外部客户程序和系统间的相互接口?如何将外部客户程序的演化和内部子系统的变化之间的依赖互相解耦?

模式定义

  • 为子系统中的一组接口提供一个一致(稳定)的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)

UML 类图

x

角色

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
  • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
  • 客户(Client)角色:通过一个外观角色访问各个子系统的功能。

代码

// 外观角色
class Facade {
  private obj1: SubSystem01 = new SubSystem01();
  private obj2: SubSystem02 = new SubSystem02();
  private obj3: SubSystem03 = new SubSystem03();

  public method(): void {
    this.obj1.method1();
    this.obj2.method2();
    this.obj3.method3();
  }
}

// 子系统角色
class SubSystem01 {
  public method1(): void {
    console.log('子系统01的method1()被调用!');
  }
}

// 子系统角色
class SubSystem02 {
  public method2(): void {
    console.log('子系统02的method2()被调用!');
  }
}

// 子系统角色
class SubSystem03 {
  public method3(): void {
    console.log('子系统03的method3()被调用!');
  }
}

export default class FacadePattern {
  public static main(args: string[]): void {
    const f: Facade = new Facade();
    f.method();
  }
}

要点总结

  • 从客户程序的角度来看,Facade模式简化了整个组件系统的接口,对于组件内部与外部客户程序来说,达到了一种“解耦”的效果——内部子系统的任何变化不会影响到Facade接口的变化。
  • Facade设计模式更注重从架构的层次去看整个系统,而不是单个类的层次。Facade很多时候更是一种架构设计模式。
  • Facade设计模式并非一个集装箱,可以任意地放进任何多个对象。Facade模式中组件的内部应该是“相互耦合关系比较大的一系列组件”,而不是一个简单的功能集合。

代理模式(Proxy)

动机

  • 在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者、或者系统结构带来很多麻烦。
  • 如何在不失去透明操作对象的同时来管理/控制这些对象特有的复杂性? 增加一层间接层是软件开发中常见的解决方式。

模式定义

  • 为其他对象提供一种代理以控制(隔离、使用接口)对这个对象的访问。

UML 类图

x

角色

  • 抽象主题( Subject )类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题( Real Subject )类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理( Proxy )类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代码

// 抽象主题
interface Subject {
  Request(): void;
}

// 真实主题
class RealSubject implements Subject {
  public Request(): void {
    console.log('访问真实主题方法...');
  }
}

// 代理
class Proxy implements Subject {
  private realSubject: RealSubject;

  public Request(): void {
    if (!this.realSubject) {
      this.realSubject = new RealSubject();
    }
    this.preRequest();
    this.realSubject.Request();
    this.postRequest();
  }

  public preRequest(): void {
    console.log('访问真实主题之前的预处理。');
  }

  public postRequest(): void {
    console.log('访问真实主题之后的后续处理。');
  }
}

export default class ProxyTest {
  public static main(args: string[]): void {
    const proxy: Proxy = new Proxy();
    proxy.Request();
  }
}

静态代理

  • Java中的静态代理要求代理类(ProxySubject)和委托类(RealSubject)都实现同一个接口(Subject)。
  • 静态代理中代理类在「编译时」确定。
  • 场景:装饰器,实际上是一种静态代理。
  • 代码(同上)

动态代理

  • UML类图

x

  • Java中的动态代理依靠「反射」来实现,代理类和委托类不需要实现同一个接口。
  • JVM 「运行时」动态生成。
  • 应用场景:cglib、Seata 的 DataSourceProxy、mybatis 的 mapper 都是动态代理
  • 代码
class AbstractHelloService {
  sayHello() {}
}

class HelloServiceImpl extends AbstractHelloService {
  sayHello() {
    console.log('Hello World!');
  }
}

class HelloServiceDynamicProxy extends AbstractHelloService {
  constructor(helloService) {
    super();
    this.helloService = helloService;
  }

  // public Object getProxyInstance() {
  //   return Proxy.newProxyInstance(
  //     helloService.getClass().getClassLoader(),
  //     helloService.getClass().getInterfaces(),
  //     new InvocationHandler() {
  //       @Override
  //       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  //         System.out.println("Before say hello...");
  //         Object ret = method.invoke(helloService, args);
  //         System.out.println("After say hello...");
  //         return ret;
  //       }
  //     }
  //   );
  // }
  getProxyInstance() {
    return new Proxy(this.helloService, {
      get(target, name, receiver) {
        const method = Reflect.get(target, name, receiver);

        return function () {
          console.log('Before say hello...');
          method.apply();
          console.log('After say hello...');
        };
      },
    });
  }
}

const helloService = new HelloServiceImpl();
const proxy = new HelloServiceDynamicProxy(helloService).getProxyInstance();
proxy.sayHello();

要点总结

  • “增加一层间接层”是软件系统中对许多复杂问题的一种常见解决方法。在面向对象系统中直接使用某些对象会带来很多问题,作为间接层的 proxy 对象便是解决这一问题的常用手段。
  • 具体 proxy 设计模式的实现方法,实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如 copy-on-write 技术,有些可能对组件模块提供抽象代理层,在架构层次对对象做 proxy。
  • Proxy 并不一定要求保持接口完整的一致性,只有能够实现间接控制,有时候损及一些透明性也是可以接受的。

适配器模式(Adapter)

动机

  • 在软件系统中,由于应用环境的变化,常常需要将「一些现存的对象」放在新的环境中应用,但是在新环境要求的接口是这些现存对象所不满足的。
  • 如何应对这种「迁移的变化」? 如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?

模式定义

  • 将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

类适配器 UML

x

对象适配器 UML

x

角色

  • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

代码

// 目标接口
interface Target {
  request(): void;
}

// 适配者接口
class Adaptee {
  public specificRequest(): void {
    console.log('适配者中的业务代码被调用!');
  }
}

// 类适配器类
class ClassAdapter extends Adaptee implements Target {
  public request(): void {
    this.specificRequest();
  }
}

// 类适配器类 客户端代码
export class ClassAdapterTest1 {
  public static main(args: string[]): void {
    console.log('类适配器模式测试:');
    const target: Target = new ClassAdapter();
    target.request();
  }
}

/* ---- 分割线 ---- */

// 对象适配器类
class ObjectAdapter implements Target {
  private adaptee: Adaptee;
  public constructor(adaptee: Adaptee) {
    this.adaptee = adaptee;
  }
  public request(): void {
    this.adaptee.specificRequest();
  }
}

// 对象适配器类 客户端代码
export default class ObjectAdapterTest2 {
  public static main(args: string[]): void {
    console.log('对象适配器模式测试:');
    const adaptee: Adaptee = new Adaptee();
    const target: Target = new ObjectAdapter(adaptee);
    target.request();
  }
}

要点总结

  • Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。
  • GoF 23 定义了两种 Adapter 模式的实现结构:对象适配器和类适配器。 但类适配器采用“多继承”的实现方式,一般不推荐使用。 对象适配器采用“对象组合”的方式,更符合 “松耦合” 的精神。
  • Adapter 模式可以实现的非常灵活,不必拘泥于 GoF 23 中定义的两种结构。例如,完全可以将 Adapter 模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。

中介者模式(Mediator)

动机

  • 在软件构建的过程中,经常会出现多个对象相互关联交互的情况,对象之间常常会维持一种复杂的引用关系;如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。
  • 在这种情况下,我们可以使用一个“中介对象”来管理对象间的关联关系,避免相互交互的对象之间的紧耦合引用关系,从而更好地抵御变化。

模式定义

  • 用一个中介对象来封装(封装变化)一系列的对象交互。 中介者使各个对象不需要显示的相互引用(编译时依赖->运行时依赖),从而使其耦合松散(管理变化),而且可以独立地改变它们之间的交互。
  • 把「网状交互关系」 变成「星状交互关系」。
  • 类之间各司其职,符合 迪米特法则

UML 类图

x

角色

  • 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  • 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  • 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  • 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

代码

// 抽象中介者
abstract class Mediator {
  public abstract register(colleague: Colleague): void;

  public abstract relay(cl: Colleague): void; // 转发
}

// 具体中介者
class ConcreteMediator extends Mediator {
  private colleagues: Array<Colleague> = new Array<Colleague>();

  public register(colleague: Colleague) {
    if (this.colleagues.indexOf(colleague) < 0) {
      this.colleagues.push(colleague);
      colleague.setMedium(this);
    }
  }

  public relay(cl: Colleague) {
    for (const ob of this.colleagues) {
      if (ob !== cl) {
        ob.receive();
      }
    }
  }
}

// 抽象同事类
abstract class Colleague {
  protected mediator: Mediator;

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

  public abstract receive(): void;

  public abstract send(): void;
}

// 具体同事类
class ConcreteColleague1 extends Colleague {
  public receive(): void {
    console.log('具体同事类1收到请求。');
  }

  public send(): void {
    console.log('具体同事类1发出请求。');
    this.mediator.relay(this); // 请中介者转发
  }
}

// 具体同事类
class ConcreteColleague2 extends Colleague {
  public receive(): void {
    console.log('具体同事类2收到请求。');
  }

  public send(): void {
    console.log('具体同事类2发出请求。');
    this.mediator.relay(this); // 请中介者转发
  }
}

export default class MediatorPattern {
  public static main(args: string[]): void {
    const md: Mediator = new ConcreteMediator();
    const c1: Colleague = new ConcreteColleague1();
    const c2: Colleague = new ConcreteColleague2();
    md.register(c1);
    md.register(c2);
    c1.send();
    console.log('-------------');
    c2.send();
  }
}

要点总结

  • 将多个对象间复杂的关联关系解耦,Mediator模式将多个对象间的控制逻辑进行集中管理,变“多个对象互相关联”为“多个对象和一个中介者关联”,简化了系统的维护,抵御了可能的变化。
  • 随着控制逻辑哦复杂化,Mdiator具体对象的实现可能相当复杂。这时候可以对Mediator对象进行分解处理。
  • Facade 模式是解耦系统间(单向)的对象关联关系;Mediator 模式是解耦系统内各个对象之间(双向)的关联关系。