Typescript

TS 设计模式06 - 代理模式

2020-09-07  本文已影响0人  love丁酥酥

1. 简介

代理,顾名思义,就是替委托者处理事情。通过代理,客户不必要去接触真实的目标对象,转而去接触目标对象的代理,即可达成目的。

2. 代理模式

代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。
当两个类需要通信时,通过代理类,可以将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理。代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

代理模式拥有以下三种角色:

作者:Jerry_1116
链接:https://www.jianshu.com/p/9cdcf4e5c27d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代理模式有多种不同的实现方式。如果按照代理创建的时期来进行分类:静态代理、动态代理。

2.1 静态代理

image.png
interface Subject {
    doOperation(...items: Array<any>): void;
}

class RealSubject implements Subject {
    doOperation(...items: Array<any>): void {
        console.log(`真实的目标对象在执行操作,参数:${items}`); // 真实的目标对象在执行操作,参数:do work,sing
    }
}

class MyProxy implements Subject {
    private target: RealSubject;
    constructor(target: RealSubject) {
        this.target = target;
    }
    doOperation(...items: Array<any>): void {
        console.log(`代理对象在执行操作,参数:${items}`); // 代理对象在执行操作,参数:do work,sing
        this.target.doOperation(...items);
    }
}

const proxy = new MyProxy(new RealSubject());
proxy.doOperation('do work', 'sing');

静态代理让业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。但是它有如下缺点:

由于静态代理的这两个缺点,就需要使用动态代理。

2.2 动态代理

说到动态代理,ES6 其实提供了 Proxy 对象,用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。这里我们定义 get 行为即可:

interface Subject {
    doOperation(...items: Array<any>): void;
}

class RealSubject implements Subject {
    doOperation(...items: Array<any>): void {
        console.log(`真实的目标对象在执行操作,参数:${items}`, this); // 真实的目标对象在执行操作,参数:do work,sing
    }
}

const proxy = new Proxy(new RealSubject(), {
    get(target, propKey) {
        console.log(`代理对象在执行操作 ${propKey}`); // 代理对象在执行操作 doOperation
        return target[propKey];
    },
});

proxy.doOperation('do work', 'sing');

当然,这里动态调用对象方法时使用 Reflect 更佳:

interface Subject {
    doOperation(...items: Array<any>): void;
}

class RealSubject implements Subject {
    doOperation(...items: Array<any>): void {
        console.log(`真实的目标对象在执行操作,参数:${items}`); // 真实的目标对象在执行操作,参数:do work,sing
    }
}

const proxy = new Proxy(new RealSubject(), {
    get(target, propKey, receiver) {
        console.log(`代理对象在执行操作 ${propKey}`); // 代理对象在执行操作 doOperation
        return Reflect.get(target, propKey, receiver);
    },
});

proxy.doOperation('do work', 'sing');

不过需要小心的是,虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。

const target = {
    m() {
        console.log(this === proxy);
    },
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m(); // false
proxy.m(); // true

有些原生对象的内部属性,只有通过正确的this才能拿到,所以 Proxy 也无法代理这些原生对象的属性。

const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate();
// Uncaught TypeError: this is not a Date object.

上面代码中,getDate 方法只能在 Date 对象实例上面拿到,如果 this 不是 Date对象实例就会报错。这时,this 绑定原始对象,就可以解决这个问题。

const target = new Date('2015-01-01');
const handler = {
  get(target, prop) {
    if (prop === 'getDate') {
      return target.getDate.bind(target);
    }
    return Reflect.get(target, prop);
  }
};
const proxy = new Proxy(target, handler);

proxy.getDate(); // 4

3. 小结

代理和装饰器模式比较像,都是在不改变原对象的情况下,又改变了原对象的某些功能。不同的地方是,装饰器主要是对原对象的新增和加强,而代理注重的是对原对象的隐藏和控制。

参考

图解23种设计模式(TypeScript版)——前端必修内功心法
代理模式 - 百度百科
代理模式 | 菜鸟教程
book-设计模式之禅
book-大话设计模式
Java的三种代理模式
设计模式之——代理模式
ES 6 系列 - Proxy
ES6 Proxy用法详解 - 简书
ES6-Proxy与数据劫持(12)
ES6入门之Proxy
mdn·proxy- JavaScript | MDN
Reflect.get() - JavaScript | MDN
阮一峰es6要点总结——Proxy
陷阱: 不是所有的的对象都可以透明的进行代理

上一篇下一篇

猜你喜欢

热点阅读