对几种常见的设计模式的理解 (javascript实现)

494 阅读5分钟

对几种常见的设计模式的理解 (javascript实现)

设计模式的核心原则:

  1. 单一职责原则(SRP)
    • 一个对象(方法)只做一件事情
  2. 开放-封闭原则(OCP)
    • 软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。
  3. 最少知识原则(LKP)
    • 一个软件实体应当尽可能少地与其他实体发生相互作用。(调用链不要太长和过于复杂)
      • 比如(情景:将军需要让(士兵 function)挖散兵坑)
      • (复杂的情况)将军通知上校让他叫来少校,然后让少校找来上尉,并让上尉通知一个军士,最后军士唤来一个士兵,然后将军命令士兵挖掘一些散兵坑。
      • (简单情况)将军有个调度部,命令调度部去挖掘一些散兵坑就行了,调度部会安排士兵去

几种常见的设计模式:

  1. 工厂模式
  2. 单例模式
  3. 迭代器模式
  4. 代理(中介)模式
  5. 适配器模式
  6. 发布-订阅模式
  7. 装饰器模式

1. 工厂模式

顾名思义,批量制造商品,商品之前是相互独立的,一个发生变更不会影响到另一个

截取axios部分源码

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);
  
  ...
  
  instance.create = function create(instanceConfig) {
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);

// 可以通过调用 .create 的方法,批量生成axios实例,每个实例都是独立的、互不影响
var myAxios = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'},
  ...
});

2. 单例模式

定义:确保一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象等

在JavaScript开发中,单例模式的用途非常广泛。试想一下,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。

单个单例参考:

// 单例构造函数
function CreateSingleton (name) {
    this.name = name;
    this.getName();
};

// 获取实例的名字
CreateSingleton.prototype.getName = function() {
    console.log(this.name)
};
// 单例对象
var Singleton = (function(){ // 用闭包特性,保存唯一的单例
    var instance; // 保存唯一的单例
    return function (name) {
        if(!instance) {
            instance = new CreateSingleton(name);
        }
        return instance;
    }
})();

// 创建实例对象1
var a = new Singleton('a');
// 创建实例对象2
var b = new Singleton('b');
console.log(a === b); // true

通用单例参考:

var singleton = function(fn) {
    var instance;
    return function() {
        return instance || (instance = fn.apply(this, arguments));
    }
};
// 创建遮罩层
var createMask = function(){
    // 创建div元素
    var mask = document.createElement('div');
    // 设置样式
    mask.style = 'xxx'
    document.body.appendChild(mask);
    // 单击隐藏遮罩层
    mask.onclick = function(){
        this.style.display = 'none';
    }
    return mask;
};

// 创建登陆窗口
var createLogin = function() {
    // 创建div元素
    var login = document.createElement('div');
    // 设置样式
    login.style = 'xxx'
    login.innerHTML = 'login it';
    document.body.appendChild(login);
    return login;
};

document.getElementById('btn').onclick = function() {
    singleton(createMask)();
    singleton(createLogin)();    
}

总结:需要有一个变量去保存单例

3. 迭代器模式

可以很简便的实现遍历

  • js也内置了迭代器(some、every、forEach、map、filter)等
// 手动实现一个迭代器
var each = function(ary, callback){
    for (var i = 0, l = ary.length; i < l; i++){
        if (callback.call(ary[i], i, ary[i]) === false){ // callback 的执行结果返回false,提前终止迭代
            break;
        }
    }
};

each([1, 2, 3, 4, 5], function(i, n){
    if (n > 3){ // n 大于3 的时候终止循环
        return false;
    }
    console.log(n); // 分别输出:1, 2, 3
});

4. 代理(中介)模式

为一个对象提供一个代用品或占位符,以便控制对它的访问

代理模式是一种非常有意义的模式,在生活中可以找到很多代理模式的场景。

  • 比如,明星都有经纪人作为代理。如果想请明星来办一场商业演出,只能联系他的经纪人。经纪人会把商业演出的细节和报酬都谈好之后,再把合同交给明星签。

代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象

proxy.png

举个实际例子:

  • 通过增加虚拟代理的方式,把预加载图片的职责放到代理对象中,而本体仅仅负责往页面中添加img标签,这也是它最原始的职责。

此例子是:我们需要加载一个大图,大图比较慢,在大图未加载好之前,先用菊花图loading.gif替代

// myImage负责往页面中添加img标签:
var myImage = (function(){
    var imgNode = document.createElement( 'img' );
    document.body.appendChild( imgNode );
    return {
        setSrc: function( src ) {
            imgNode.src = src;
        }
    }
})();

// proxyImage负责预加载图片,并在预加载完成之后把请求交给本体myImage:
var proxyImage = (function() {
    var img = new Image;
    img.onload = function() {
        myImage.setSrc( this.src );
    }
    return {
        setSrc: function( src ) {
            myImage.setSrc( './loading.gif' );
            img.src = src;
        }
    }
})();

proxyImage.setSrc( 'http://imgcache.qq.com/music/photo/xxxxxxbig.png' );

5. 适配器模式

axios源码用了适配器模式去发起请求,适配器会根据当前的环境,如果是浏览器环境 就用XHR对象发请求,如果是node环境,就用http模块发请求

截取axios源码某段

var adapter = function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}

return adapter(config).then(function onAdapterResolution(response) {
  throwIfCancellationRequested(config);

  // Transform response data
  response.data = transformData.call(
    config,
    response.data,
    response.headers,
    config.transformResponse
  );

  return response;
}, function onAdapterRejection(reason) {
  ...
  return Promise.reject(reason);
});

6. 发布-订阅模式(观察者模式)

博主姊妹篇:发布-订阅模式详解(观察者模式)

7. 装饰器模式

其他伙伴写的:segmentfault.com/a/119000001…


个人整理,有误可留言。 部分参考丛书 《JavaScript设计模式与开发实践》曾探