delegates简单实现委托模式

来源:网络 文章列表 2018-12-09 8
最近发现一个有意思的模块 - delegates,它由大名鼎鼎的 TJ 所写,可以帮我们方便快捷地使用设计模式当中的委托模式(Delegation Pattern),即外层暴露的对象将请求委托给内部的其他对象进行处理,当前版本是 1.0.0,周下载量约为 364 万。用法

用法

delegates 基本用法就是将内部对象的变量或者函数绑定在暴露在外层的变量上,直接通过 delegates 方法进行如下委托,基本的委托方式包含:

  • getter:外部对象可以直接访问内部对象的值
  • setter:外部对象可以直接修改内部对象的值
  • access:包含 getter 与 setter 的功能
  • method:外部对象可以直接调用内部对象的函数
const delegates = require('./index');

const petShop = {
  dog: {
    name: '旺财',
    age: 1,
    sex: '猛汉',
    bar() {
      console.log('bar!');
    }
  },
}

// 将内部对象 dog 的属性、函数
// 委托至暴露在外的 petShop 上
delegates(petShop, 'dog')
  .getter('name')
  .setter('age')
  .access('sex')
  .method('bar');

// 访问内部对象属性
console.log(petShop.name)
// => '旺财'

// 修改内部对象属性
petShop.age = 2;
console.log(petShop.dog.age)
// => 2

// 同时访问和修改内部对象属性
console.log(petShop.sex)
// => '猛汉'
petShop.sex = '公主';
console.log(petShop.sex);
// => '公主'

// 调用内部对象函数
petShop.bar();
// 'bar!'

除了上面这种方式之外,还可以在外部对象上添加类似 jQuery 风格的函数,即:

  • 函数不传参数的时候,获取对应的值
  • 函数传参数的时候,修改对应的值
const delegates = require('./index');

const petShop = {
  dog: {
    name: '旺财',
  },
}

delegates(petShop, 'dog')
  .fluent('name');

// 不传参数,获取内部属性
console.log(petShop.name());

// 传参数,修改内部属性
// 还可以链式调用
console.log(
    petShop.name('二哈')
        .name('蠢二哈')
        .name();
);

源码学习

初始化

// 源码 7 - 1
function Delegator(proto, target) {
  if (!(this instanceof Delegator)) return new Delegator(proto, target);
  this.proto = proto;
  this.target = target;
  this.methods = [];
  this.getters = [];
  this.setters = [];
  this.fluents = [];
}

getter

// 源码 7-2
Delegator.prototype.getter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.getters.push(name);

  proto.__defineGetter__(name, function(){
    return this[target][name];
  });

  return this;
};

上面代码中的关键在于 __defineGetter__ 的使用,它可以在已存在的对象上添加可读属性,其中第一个参数为属性名,第二个参数为函数,返回值为对应的属性值:

const obj = {};
obj.__defineGetter__('name', () => 'elvin');

console.log(obj.name);
// => 'elvin'

obj.name = '旺财';
console.log(obj.name);
// => 'elvin'
// 我怎么能被改名叫旺财呢!

需要注意的是尽管 __defineGetter__ 曾被广泛使用,但是已不被推荐,建议通过 Object.defineProperty 实现同样功能,或者通过 get 操作符实现类似功能:

const obj = {};
Object.defineProperty(obj, 'name', {
  value: 'elvin',
});

Object.defineProperty(obj, 'sex', {
  get() {
    return 'male';
  }
});

const dog = {
  get name() {
    return '旺财';
  }
};

Github 上已有人提出相应的 PR#20,不过因为 TJ 已经离开了 Node.js 社区,所以估计也不会更新这个仓库了。

setter

// 源码 7-3
Delegator.prototype.setter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.setters.push(name);

  proto.__defineSetter__(name, function(val){
    return this[target][name] = val;
  });

  return this;
};

上述代码与 getter 几乎一模一样,不过使用的是 __defineSetter__,它可以在已存在的对象上添加可读属性,其中第一个参数为属性名,第二个参数为函数,参数为传入的值:

const obj = {};
obj.__defineSetter__('name', function(value) {
  this._name = value;
});

obj.name = 'elvin';
console.log(obj.name, obj._name);
// undefined 'elvin'

同样地,虽然 __defineSetter__ 曾被广泛使用,但是已不被推荐,建议通过 Object.defineProperty 实现同样功能,或者通过 set 操作符实现类似功能:

const obj = {};
Object.defineProperty(obj, 'name', {
  set(value) {
    this._name = value;
  }
});

const dog = {
  set(value) {
    this._name = value;
  }
};

method

// 源码 7-4
Delegator.prototype.method = function(name){
  var proto = this.proto;
  var target = this.target;
  this.methods.push(name);

  proto[name] = function(){
    return this[target][name].apply(this[target], arguments);
  };

  return this;
};

method 的实现也十分简单,只需要注意这里 apply 函数的第一个参数是内部对象 this[target],从而确保了在执行函数 this[target][name] 时,函数体内的 this 是指向对应的内部对象。

其它 delegates 提供的函数如 fluent | access 都是类似的,就不重复说明了。

koa 中的使用

在 koa 中,其核心就在于 context 对象,许多读写操作都是基于它进行,例如:

  • ctx.header 获取请求头
  • ctx.method 获取请求方法
  • ctx.url 获取请求 URL

这些对请求参数的获取都得益于 koa 中 context.request 的许多属性都被委托在了 context 上:

// Koa 源码 lib/context.js
delegate(proto, 'request')
  .method('acceptsLanguages')
  .method('acceptsEncodings')
  .access('path')
  .access('url')
  .getter('headers')
  .getter('ip');
  // ...

又例如:

  • ctx.body 设置响应体
  • ctx.status 设置响应状态码
  • ctx.redirect() 请求重定向

这些对响应参数的设置都得益于 koa 中 context.response 的许多属性和方法都被委托在了 context 上:

// Koa 源码 lib/context.js
delegate(proto, 'response')
  .method('redirect')
  .method('vary')
  .access('status')
  .access('body')
  .getter('headerSent')
  .getter('writable');
  // ...

 

腾讯云限量秒杀

1核2G 5M 50元/年 2核4G 8M 74元/年 4核8G 5M 818元/年 CDN流量包 100GB 9元

版权声明

本站部分原创文章,部分文章整理自网络。如有转载的文章侵犯了您的版权,请联系站长删除处理。如果您有优质文章,欢迎发稿给我们!联系站长:
愿本站的内容能为您的学习、工作带来绵薄之力。

评论

  • 随机获取
点击刷新
精彩评论

友情链接