Rust·设计模式·接口隔离模式

 

门面模式: 简化外部客户和内部系统的耦合 Proxy模式:增加间接层,实现不为外界所知的功能 Adaper模式:将一个类的接口转换成客户希望的另一个接口 Mediator: 将密集的、大量的多个对象间复杂的关联关系解耦

  • 门面模式: 简化外部客户和内部系统的耦合
  • Proxy模式:增加间接层,实现不为外界所知的功能
  • Adaper模式:将一个类的接口转换成客户希望的另一个接口
  • Mediator: 将密集的、大量的多个对象间复杂的关联关系解耦

接口隔离模式

  • 某些接口之间的依赖会带来问题
    • 通过增加一层间接(稳定)接口,隔离相互紧密相关的接口
  • 典型模式
    • Facade
    • Proxy
    • Adapter
    • Mediator

门面模式

系统间复杂的耦合

动机

  • 组件的客户和组件的子系统之间有了复杂的耦合
    • 随着外部客户端和子系统的演化
    • 过多的耦合会造成很多问题
  • 如何简化外部客户和内部系统的耦合?
  • 如何将外部客户程序的演化,和内部子系统之间的依赖相互隔离?

模式定义

  • 为系统中的一组接口提供一个对外稳定的、一致的界面,Facade模式定义了一个高层接口,这个接口使得这个系统更加容易使用
  • 它是一个接口,它知道某个请求可以交由哪个子系统进行处理
  • 它使用组合将客户端的请求委派给相应的子系统对象

结构

Facade模式结构

要点总结

  • 从客户角度,Facade模式简化了系统的接口
    • 对于组件的内部程序和外部客户来说,达到了解耦的效果
      • 内部子系统的变化不会影响到Facade接口的变化
  • Facade模式注重从架构层次看这个系统
    • 不是单个的类视角
  • Facade模式不是集装箱
    • 内部的子系统是相互耦合关系较大的一系列组件
    • 而不是简单的工具箱(功能的集合)

Rust 实现

实现功能

假设你要在家中举行一场婚礼,并且由你来张罗这一切。这真是一个艰巨的任务。你必须预订一家酒店或场地,与餐饮人员交代酒菜、布置场景,并安排背景音乐。 在不久以前,你已经自己搞定了一切,例如找相关人员谈话、与他们进行协调、敲定价格等,那么现在你就很轻松了。此外,你还可以去找会务经理,让他/她为你处理这些事情。会务经理负责跟各个服务提供商交涉,并为你争取最优惠的价格

  • 我们从门面模式的角度来看待这些事情:
    • 客户端
      • 你需要在婚礼前及时完成所有的准备工作。每一项安排都应该是顶级的,这样客人才会喜欢这些庆祝活动
    • 门面
      • 会务经理负责与所有相关人员进行交涉,这些人员负责处理食物、花卉装饰等
    • 子系统
      • 它们代表提供餐饮、酒店管理和花卉装饰等服务的系统

具体实现

#![feature(non_ascii_idents)]

struct 活动经理;
impl 活动经理 {
    fn 安排(&mut self) {
        酒店.定酒店();
        花店.定花();
        乐队.音乐类型();
    }
}

struct 酒店;
impl 酒店 {
    fn 有空房(&self) -> bool {
        println!("[酒店] 指定日期那天有空房");
        true
    }
    fn 定酒店(&self) {
        println!("[酒店] 为结婚安排酒店?");
        if self.有空房() {
            println!("[酒店] 房间已预定好\n");
        }
    }
}
struct 花店;
impl 花店 {
    fn 定花(&self) {
        println!("[花店] 你需要什么鲜花装饰?");
        println!("[花店] 已预定好玫瑰、茉莉和康乃馨\n");
    }
}
struct 乐队;
impl 乐队 {
    fn 音乐类型(&self) {
        println!("[乐队] 你需要什么风格的音乐?");
        println!("[乐队] 准备当天演奏爵士和古典音乐\n");
    }
}

struct 承办人;
impl 承办人 {
    fn 问活动经理(&self) {
        println!("[你]: 我来找找活动经理");
        let mut 经理 = 活动经理;
        经理.安排();
    }
}

fn main() {
    let  = 承办人;
    println!("老爸:给你哥的婚礼安排得怎么样了?");
    你.问活动经理();
}

运行结果:

老爸:给你哥的婚礼安排得怎么样了?
[你]: 我来找找活动经理
[酒店] 为结婚安排酒店?
[酒店] 指定日期那天有空房
[酒店] 房间已预定好

[花店] 你需要什么鲜花装饰?
[花店] 已预定好玫瑰、茉莉和康乃馨

[乐队] 你需要什么风格的音乐?
[乐队] 准备当天演奏爵士和古典音乐

Proxy 模式

动机

  • 有些对象由于某些原因,直接访问会给使用者或系统结构带来麻烦
    • 对象创建的开销很大
    • 某些系统操作需要安全控制
    • 或需要进程外的访问
      • 本机要在其他机子上创建对象(分布式计算)
  • 如何在不失去透明操作对象的同时来管理/控制这些对象特有的复杂性
    • 增加一层间接层是软件开发中常见的解决方式
    • 透明
      • 在本机创建对象的方式,也可以在其他机子上创建对象
      • 但是那些复杂操作(创建对象、安全控制)对客户端不可见

模式定义

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

结构

初始设计

class ISubject{
public:
    virtual void process();
};

class RealSubject: public ISubject{
public:
    virtual void process(){
        //....
    }
}

class ClientApp{
    ISubject* subject;
public:
    // 有可能得不到这个对象    
    ClientApp(){
        subject=new RealSubject();
    }
    
    void DoTask(){
        //...
        subject->process();
        //....
    }
};

重构设计

class ISubject{
public:
    virtual void process();
};


//Proxy的设计
class SubjectProxy: public ISubject{
public:
    virtual void process(){
        //对RealSubject的一种间接访问
        //....可能是网络访问、安全控制
    }
};

class ClientApp{
    ISubject* subject;
public:
    // 通过代理来创建real suject对象
    ClientApp(){
        subject=new SubjectProxy();
    }
    
    void DoTask(){
        //...
        subject->process();
        //....
    }
};

要点总结

  • 增加中间层是软件系统应对复杂问题的一种解法
    • 如果直接使用某些对象会造成问题
    • 作为间接层的Proxy是解决此问题的一种手段
  • 具体Proxy的实现方法、实现细粒度相差很大
    • 对单个对象细粒度的控制:Copy-on-Write
    • 对整个组件模块提供抽象代理层
      • 在架构层次对对象做Proxy
  • Proxy不要求保持接口完整的一致性
    • 主要能实现间接控制
    • 损失一定的透明度是可以接受的
  • Proxy核心
    • 增加间接层,实现不为外界所知的功能

Adaper模式

动机

  • 由于应用环境的变化
    • 需要将现有的对象放到新的环境中
    • 但新环境要求的接口是这些对象不能提供的
  • 如何应对这种迁移性质的变化?
    • 如何既利用现有对象的良好实现
    • 有满足新应用环境要求的接口?

模式定义

  • 将一个类的接口转换成客户希望的另一个接口

结构

  • Adaptee:被适配者,以前的接口
  • Target:最后转换成的目标
  • 继承一个类(is a)
    • 我遵循这个类定义的接口规范
    • Adapter具备Target的接口规范
  • 组合一个类(has a)
    • 支持一个类的实现方式

初始设计

//目标接口(新接口)
class ITarget{
public:
    virtual void process()=0;
};

//遗留接口(老接口)
class IAdaptee{
public:
    virtual void foo(int data)=0;
    virtual int bar()=0;
};

//遗留类型
class OldClass: public IAdaptee{
    //....
};

//对象适配器
class Adapter: public ITarget{ //继承
protected:
    IAdaptee* pAdaptee;//组合
public:
    Adapter(IAdaptee* pAdaptee){
        this->pAdaptee=pAdaptee;
    }
    
    // 老接口转换成新接口
    virtual void process(){
        int data=pAdaptee->bar();
        pAdaptee->foo(data);
    }
};

//类适配器
class Adapter: public ITarget,
               protected OldClass{ //多继承
}

int main(){
    IAdaptee* pAdaptee=new OldClass();
    // 旧的类放到适配器后,可以当新的类用
    ITarget* pTarget=new Adapter(pAdaptee);
    pTarget->process();
}
  • 设计模式的应用
    • 设计模式有很多变体模式
    • 和经典的定义不同,不需要精确符合定义
  • 关键
    • 识别设计模式的场景
    • 解决问题的手法
    • 对变化点和稳定点的分离

要点总结

  • Adapter主要应用于
    • 希望复用一些现有的类
    • 但接口又和应用环境不一致的情况

中介者模式

动机

  • 在软件的构建中,会出现多个对象互相关联交互的情况
    • 对象之间常常会维持一种复杂的引用关系
    • 如果遇到一些需求更改
    • 这种直接引用的关系会面临许多更改
    • 比如
      • 界面和数据之间的关系
      • 界面的控件会引用数据对象
      • 数据对象也会关联多个界面控件
  • 使用中介对象来管理对象的关联关系
    • 避免相互交互的对象间的紧耦合引用关系
    • 更好的抵御变化

模式定义

  • 用一个中介对象来封装一系列的对象交互
  • 中介者使对象不需要显示的相互引用
    • 编译时依赖-> 运行时依赖
      • A –> IB
      • B –> IB
      • 在编译时A不依赖B,但在运行时A依赖B
    • 从而使耦合变松散(管理变化)
    • 可以独立的改变它们之间的交互

结构

  • Colleague内部有一个指针指向了Mediator
  • ConcreteMediator中有指针指向了 ConcreteColleague1/2
  • 核心
    • Colleague之间没有依赖关系
    • 本来它们之间有依赖关系的,现在没有了
    • 它们都依赖Mediator
    • 而 Mediator 内部又会依赖 Colleague
    • 所以 Mediator 和 Colleague 之间是双向依赖关系
    • 但 Colleague 之间不再有依赖
    • 达到依赖的解耦
  • 下面的对象之间先是随意的相互依赖,现在大家都依赖中介者Mediator
  • 当1要通知3时,1要通知M,再由M通知3
  • 这时需要定义一个消息通知机制

中介者

要点总结

  • 将密集的、大量的多个对象间复杂的关联关系解耦
    • 将多个对象间的控制逻辑集中管理
    • 将多个对象的关联变为多个对象和一个中介者的关联
  • 随着控制逻辑的复杂
    • Mediator具体对象的实现可能很复杂
    • 这时可以对Medaitor对象进行分解处理
  • Facade是解耦系统间(单向)的关联关系
    • Meidator是解耦系统内各对象间(双向)的关联关系