设计模式 - 代理模式(Proxy Design Pattern)

136 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

设计模式 - 代理模式(Proxy Design Pattern)

目的:

不改变原始类代码的情况下,通过引入代理类给原始类附加功能。

更好的使用服务,特别是在服务对象比较庞大,或者需要大量重复额外的处理。

命名方式

JavaScript中代理模式的具体表现形式就是ES6中的新增对象--- Proxy

Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

对js中一切合理的对象的行为和属性进行自定义操作。对象在执行本身的方法或者获取对象本身属性时,其执行的过程和结果是你自定义的,而不是对象的。

语法:const proxy = new Proxy(target, handler)

Proxy方法需要传入两个参数,分别是需要被Proxy代理的对象和捕获对象行为的一系列捕获器传送门, 并返回一个新对象proxyproxy拥有target的一切属性和方法。只是其行为与结果在handler中进行自定义操作。

Proxy中共有13个捕获器,它们用于我们对对象、函数的方法调用监听。下面是Proxy捕获器以及它们的触发条件。

对象中的方法对应触发条件
handler.getPrototypeOf()Object.getPrototypeOf 方法的捕捉器
handler.setPrototypeOf()Object.setPrototypeOf 方法的捕捉器
handler.isExtensible()Object.isExtensible 方法的捕捉器
handler.preventExtensions()Object.preventExtensions 方法的捕捉器
handler.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor 方法的捕捉器。
handler.defineProperty()Object.defineProperty 方法的捕捉器
handler.has()in 操作符的捕捉器
handler.get()属性读取操作的捕捉器handler.set()属性设置操作的捕捉器
handler.deleteProperty()delete 操作符的捕捉器
handler.ownKeys()Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器
handler.apply()函数被apply调用操作的捕捉器
handler.construct()new 操作符的捕捉器

以get捕获器为例:

// 定义一个普通的对象
const obj = { name: "a" }; 

// 代理obj这个对象,并传入get捕获器 
const objProxy = new Proxy(obj, { 
    // get捕获器 
    get: function (target, key) { 
       console.log(`捕获到对象获取${key}属性的值操作`); 
        if (key === 'age') {
          return 18;
        }
        return target[key]; 
    }, 
});

console.log(obj.name);  // a
console.log(obj.age);  // undefined

// 通过代理对象操作obj对象 
console.log(objProxy.name); // a
console.log(objProxy.age); // 18

适用场景

什么时候需要使用代理模式?

缓存代理

将一些开销很大的方法的运算结果进行缓存,再次调用该函数时,若参数一致,则可以直接返回缓存中的结果,而不用再重新进行运算


const cache = {};
const getData = (function() {
    return function(url) {
        if (cache[url]) {
            return Promise.resolve(cache[url]);
        }
        return get(url).then((res) => {
            cache[url] = res;
            return res;
        }).catch(err => console.error(err))
    }
})();
getData('/getData'); // 发起http请求
getData('/getData'); // 返回缓存数据

const cache = {};

const getMoreData = (url) => {
    return get(url).then((res) => {
            return res;
        }).catch(err => console.error(err))
  }
}

function getMoreDataCacheProxy(fn){
    return new Proxy(fn, {
        apply: (target, context, args)=> {
            const argString = args.join(' ');

            if(cache.has(argString)) {
                return cache[argString];
            }

            let res = Reflect.apply(target, context, args);
            cache.set(argString, res);
            return res;
        }
    });
}

// 使用
let proxy = getMoreDataCacheProxy(getMoreData);
let res1 = proxy('/getData'); // 发起http请求
let res2 = proxy('/getData'); // 返回缓存数据

保护代理

保护代理用于控制不同权限的对象对目标对象的访问

class Car {
    drive() 
    { return "driving"; }; 
} 

class CarProxy { 
    constructor(driver) {
        this.driver = driver; 
    } 
    drive() { 
      // 保护代理,仅18岁才能开车 
      return (this.driver.age < 18) ? "too young to drive" : new Car().drive(); 
    };
}

const carProxy = new Proxy(Car, {
    get: function (target, key) { 
       console.log(`捕获到对象获取${key}属性的值操作`); 
        if (key == 'age' && target[key] < 16) {
          return "too young to drive";
        }
        return Reflect.apply(target, drive()); 
    }, 
});

更多的场景:类私有属性的保护(不可以随意获取/修改私有属性的值)。 赋值验证校验。

虚拟代理

访问一个需要消耗大量资源的对象

图片加载


export default class ImageProxy extends React.Component {
  render() {
    return <View>
       <Image source={loading.png} style={this.state.isLoad ? {展示} : {隐藏}} />
       <Image uri={{uri: this.props.uri}}  onLoad={() => {
          this.setState({
            isLoad: false
          }) 
       }}/>
    </View>
  }
}

每个客户端都创建一个访问数据库的对象,导致查询很缓慢。

image.png

通过代理类,将代理对象更新给所有客户端,这样客户端访问代理对象后,代理对象会创建实际访问数据库对象,进行处理。

image.png

附加说明

代理与继承的区分。继承的方式会直接继承父类的所有方法,达不到按需控制访问权限的灵活效果,代理比继承更加灵活