前端设计模式的思考

好的代码应该有几个特点

  • 健壮性
  • 可读性
  • 可扩展性
  • 复用性

要遵从几个原则

  • 开闭原则
  • 接口隔离
  • 单一职责
  • 迪米特法则
  • 里氏替换

设计模式中有不同的方式,能够解决某些问题

工厂模式

目的:让我们不用关心对象的创建

1.简单工厂函数适用场景

*   正确传参,就可以获取所需要的对象,无需知道内部实现细节;
*   内部逻辑(工厂函数)通过传入参数判断实例化还是使用哪些类;
*   创建对象数量少(稳定),对象的创建逻辑不复杂;

2.简单工厂函数不适用场景

*   当需要添加新的类时,就需要修改工厂方法,这违背了开放封闭原则(OCP, 对扩展开放、对源码修改封闭)。正所谓成也萧何败也萧何。函数 `create` 内包含了所有创建对象(构造函数)的判断逻辑代码,如果要增加新的构造函数还需要修改函数 `create`(判断逻辑代码),当可选参数 `role` 变得更多时,那函数 `create` 的判断逻辑代码就变得臃肿起来,难以维护。
*   不适用创建多类对象
复制代码
建造者模式

建造者模式又称为生成器模式,它是一种较为复杂、使用频率相对较低的创建型模式。建造者模式为客户端返回的不是一个简单的产品,而是一个由多个部件组成的复杂产品

将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。

var Dialog = function (){
  this.type = '';
  this.name = '';
  this.element = '';
  this.show = function(){
    console.log(this.name + ': ' + this.type + this.element);
  }
}
 
var noticeBuilder = function(){
  this.dialog = new Dialog();
  this.setType = function(){
    this.dialog.type = 'notice';
  }
  this.setName = function(){
    this.dialog.name = '公告';
  }
  this.setElement = function(){
    this.dialog.element = '<div>notice</div>';
  }
  this.getDialog = function(){
    return this.dialog;
  }
}
 
var toastBuilder = function(){
  this.dialog = new Dialog();
  this.setType = function(){
    this.dialog.type = 'toast';
  }
  this.setName = function(){
    this.dialog.name = '提示';
  }
  this.setElement = function(){
    this.dialog.element = '<div>toast</div>';
  }
  this.getDialog = function(){
    return this.dialog;
  }
}
 
function construct(ab){
  ab.setType();
  ab.setName();
  ab.setElement();
  return ab.getDialog();
}
 
var notice = new noticeBuilder();
var toast = new toastBuilder();
var noticeIns = construct(notice);
var toastIns = construct(toast);
 
noticeIns.show(); //公告: notice<div>notice</div>
toastIns.show(); //提示: toast<div>toast</div>


复制代码

建造者模式总结:

**优点:
*** 建造者模式中,客户端不需要知道产品内部组成的细节,将产品使用与其创建解耦,使得相同创建过程可以创建不同的产品对象
* 每个具体的建造类都相对独立,方便替换和新增,满足开关原则

缺点:
* 建造者模式需要多个产品存在相同的创建流程,如果产品差异性大,就不适用建造者模式。
* 如果产品内部结构复杂多变,就需要定义很多建造类来实现这种变化,会导致系统变得庞大

单列模式

目的: 让一个类只能实例化一次

做法 通过一个标识来判断是否已经实例化

应用方向: 比如存储器 vuex vue-router

var mySingleton=(function(){
  //构造器函数
  function singleton(options){
      options=options || {}
      this.name='SingletonTestor';
      this.pointX=options.pointX || 6
      this.pointY=options.pointY || 10;
  }
 function init(){
   let now=new Date() //私有方法
   this.name='公共的属性'
   this.getISODate()=function(){
       return now.toISOString();
   }
 }
  //缓存单例的变量
  var instance;
  var _static={
      name:'SingletonTestor',
      getInstance:function(options){
          if(!instance){
              instance=new singleton(options)
          }
          return instance
      }
  }

  return _static
})()
var singletonTest=mySingleton.getInstance({
   pointX:5,
   pointY:5
})
console.log(singletonTest)  //singleton { name: 'SingletonTestor', pointX: 5, pointY: 5 }
var singletonTest1=mySingleton.getInstance({
   pointX:10,
   pointY:10
})
console.log(singletonTest1) // singleton { name: 'SingletonTestor', pointX: 5, pointY: 5 }
// 单例模式只会创建一次对象,所以第二次返回的之前创建好的
复制代码

全局对象存储

function store(){
  if(store.instance){
      return store.instance
  }
store.instance=this
}
//同
function store(){
  if(!(this instanceof store)){
      return new store()
  }
}
复制代码

上面得代码 可以new 也可以直接使用 ,这里使用了一个静态变量instance来记录是否有进行过实例化,如果实例化了就返回这个实例,如果没有实例化说明使第一次调用,就会把this赋给这个静态变量,因为是使用new 调用,这时候得this指向得就是实例化出来得对象,并且最后会隐式得返回this

var  a=new store()
var b=store()
a==b
复制代码
装饰者模式

目的 帮我们去更好的扩展方法代替继承去更好的扩展

应用场景 当我们去扩展内容又不好去修改的时候

​ 1.重写新方法2.调用老方法3.加上新操作

class Circle {
  draw(){
      console.log("画一个圆形");
  }
}

class Decorator {
  constructor(circle){
      this.circle = circle;
  }

  draw(circle){
      this.circle.draw();
      this.setRedBorder(circle);
  }

  setRedBorder(circle){
      console.log("设置红色边框");
  }
}

//测试代码
let circle = new Circle();
circle.draw(); //画了一个圆形

//装饰器的作用
let decorator = new Decorator(circle);                                                                                                                                      
decorator.draw(); //画了一个圆形,并设置了红色边框

复制代码
适配器模式

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。简而言之,就两个点:

  • 旧接口格式或者使用者不兼容。
  • 中间加一个适配转换接口
var a = function () {
    b()
}
// 原本我们使用a方法,但是现在 a方法被改名了 改为b方法, 我们就使用适配器的方式
复制代码
桥接模式

目的: 抽出代码中的公共部分,提高代码复用率

场景: 发现写了一大堆核心代码一致,但有些细节不同的方法

优点:

* 桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合开关原则。
* 多数情况下,桥接模式可以取代多层集成方案。
* 分离接口及其实现部分,使得实现可以沿着各自的维度来变化。

缺点:

* 桥接模式的使用会增加系统的理解与设计难度。
* 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

JavaScript 中桥接模式的典型应用是:Array对象上的forEach函数。

此函数负责循环遍历数组每个元素,是抽象部分;
而回调函数callback就是具体实现部分。

1、最简单模拟forEach方法:

let forEach = (arr, callbak) => {
    if (Object.prototype.toString.call(callbak) != "[object Function]" || !Array.isArray(arr)) 
        return;
    for (let i = 0; i < arr.length; i++) {
        callbak(arr[i], i);
    }
}

let arr = [1, 2, 3];
forEach(arr, (v, i) => {
    arr[i] *= 2;
})
复制代码

2、桥接模式在事件监听中的应用

function getBeerById(id, callback) {
    asyncRequest('GET', 'beer.url?id=' + id, function(resp) {
        callback(resp.responseText)
    });
}
elem.addEventListener('click', getBeerByIdBridge, false);
//创建一个桥,就可以获取当前的上下文对象this
function getBeerByIdBridge(e) {
    console.log(this) //当前点击的node节点
    getBeerById(this.id, function(beer) {
        console.log('Requested Beer: ' + beer);
    });
}
复制代码
外观模式

目标 简化多个子系统的调用

外观模式的作用是:这个外观类为子系统提供一个共同的对外接口;客户对象通过一个外观接口读写子系统中各接口的数据资源;一个客户“you”通过外观接口“computer”获取计算机内部复杂的系统信息。

客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易;实现了子系统与客户端之间的松耦合关系。

在以下情况下可以考虑使用外观模式:

(1)设计初期阶段,应该有意识的将不同层分离,层与层之间建立外观模式。

(2) 开发阶段,子系统越来越复杂,增加外观模式提供一个简单的调用接口。

(3) 维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互。

观察者模式和发布/订阅模式

观察者模式: 比较概念的解释是,目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法。

发布/订阅模式: 订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。

虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享