Zhengrenzhe

(°∀°)ノ 老打杂了

用户工具

站点工具


设计模式:模式:代理模式与aop编程

代理模式与AOP编程(Proxy Pattern)

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

代理模式非常好理解,只要提供一种机制,使它能代理其他对象的访问,就是代理模式。那么为什么非得要通过代理访问,而不直接访问源对象呢,这不是多此一举吗?

考虑以下场景,你的纯业务写在一个类或方法中,而你要对这个业务进行埋点、记录日志,难不成要把这些非业务代码加在纯业务逻辑里么?这显然是不合适的。

所以我们可以使用代理模式创建一个代理对象,用于代理业务逻辑本身,纯业务逻辑不发生变更,业务埋点、记录日志这些行为全都放在代理对象中进行,这样就合理多了。

abstract class DialogCls {
    show() {
    }

    hide() {
    }
}

class Dialog extends DialogCls {
}

class DialogProxy {
    private proxyTarget: DialogCls;

    constructor(target: DialogCls) {
        this.proxyTarget = target;
    }

    hide(): void {
        console.log("call hide");
        this.proxyTarget.show();
    }

    show(): void {
        console.log("call show");
        this.proxyTarget.hide();
    }
}

const dialogProxy = new DialogProxy(new Dialog());

dialogProxy.show();
dialogProxy.hide();

类DialogProxy实现了抽象类DialogCls的方法,当调用DialogCls实例的方法时,先进行日志记录等额外工作,然后再调用被代理对象的真实方法,这就是代理模式最简单的应用。

这种实现方式有些麻烦,每次我们都得新建两个实例才能真正工作,要是一不小心调用错了实例,那可能代理就不工作了,所以对上面的例子进行修改。

abstract class DialogCls {
    private readonly proxy: DialogProxy;

    constructor() {
        this.proxy = new DialogProxy(this);
    }

    getProxy() {
        return this.proxy;
    }

    show() {
    }

    hide() {
    }
}

// ...

const dialogProxy = new Dialog().getProxy();

dialogProxy.show();
dialogProxy.hide();

只需要在抽象类DialogCls中添加一个getProxy方法即可。

动态代理

上面的例子需要对每一个要代理的对象都创建一个代理类,也就是静态代理。假设我们对很多对象都要添加记录日志的功能,那就需要创建很多代理类来完成了,这显然有点麻烦,那么可不可以创建一个通用的代理类,用来代理所有对象呢?显然是可以的。

abstract class DialogCls3 {
    private readonly proxy: this;

    constructor() {
        this.proxy = createProxy(this);
    }

    show() {
    }

    hide() {
    }

    getProxy() {
        return this.proxy;
    }
}

class Dialog3 extends DialogCls3 {

}

function createProxy<T extends Object>(target: T) {
    return new Proxy(target, {
        get(target: T, p: string | number | symbol, receiver: any): any {
            console.log(`[log] ${ p.toString() } called`);
            return Reflect.get(target, p, receiver);
        },
    });
}


const dialog3 = new Dialog3().getProxy();
dialog3.hide();
dialog3.show();

创建一个动态代理,可以使用JavaScript中的Proxy与Reflect来完成。现在任何方法都会经过Proxy及Reflect才能被调用,从此我们再也不用一个个手写静态代理了。有了动态代理,我们可以大批量的对象应用同一种行为。

AOP编程

AOP全称为“Aspect Oriented Program”,也就是“面向切面编程”。也许你会疑惑。切面?我写个代码还要动刀吗?其实这个切面是一个抽象概念,指某个对象层次结构中可替换的层,虽然还是优点难以理解,但举个例子就能一下子明白:中间件或者生命周期函数,都可以理解为切面。

假设我们有某个业务逻辑,它分成了几步,那么在这几步中间的地方就是切面,假设有beforeSendRequest, afterSendRequest, afterProcessData这三个函数可以由用户自定义实现,那么原本静态的业务逻辑加上动态的切面函数,就构成了AOP。

koa.js的洋葱模式,React的各种生命周期都可以理解为AOP,也就是在程序的声明周期或者流程中可以动态的加减流程。那么AOP和我们的代理模式有什么关系?

我们使用代理模式,一般是为原来的对象添加非业务逻辑上的功能,例如日志、埋点,或是将公共逻辑提取出来。那么现在就会有两种角色:

  • 被代理者(纯业务逻辑)
  • 各种代理(例如日志代理,埋点代理,公共逻辑代理)

但问题是现在代理本身和代理所承担的逻辑是混合在一起的,例如代理要将被代理对象放到新创建的线程中执行,这是代理本身的行为,而代理本身所承担的逻辑是要记日志的,那么他们放在一起就不太合适了。现在AOP就可以发挥作用了。

我们把代理本地两个逻辑之间的地带作为切入点,将记日志逻辑传递进代理,这样就组合成了新创建线程执行并记日志的代理。

现在整个代理模型变成了三个角色:

  • 被代理者(纯业务逻辑)
  • 各种代理逻辑(例如日志代理,埋点代理,公共逻辑代理)
  • 代理本地(例如直接执行,新建线程执行,新建进程执行)

使用了AOP,代码逻辑上划分的更清晰了。

设计模式/模式/代理模式与aop编程.txt · 最后更改: 2020/03/05 11:56 由 zhengrenzhe