简明JavaScript设计模式

零、软件设计原则与设计模式的分类

0.1 软件设计原则-SOLID

0.1.1  单一职责原则-SRP

误解:每个模块应该只完成一个功能,我们在将大型函数重构成小函数驶经常会用到,但这只是面向底层实现细节的设计原则。

任何一个软件模块都应该只对某一类行为者负责。

0.1.2  开闭原则-OCP

软件系统应允许通过新增代码来修改系统行为,而非只能靠修改原来的代码。

0.1.3 里氏替换原则-LSP

如果想用可替换的组件来构建软件系统,那么这些组件就必须遵守同一个约定,以便让这些组件可以相互替换。

0.1.4  接口隔离原则-ISP

我们这里的User1,User2,User3都是依赖OPS的,但是User1只需要用op1,User2用op2,User3用op3。在这种情况下,虽然User1不会和op2,op3产生直接的调用关系,但在源代码层次上也与他们形成依赖关系。这种依赖关系会导致两个问题:

  • 修改op2,op3的逻辑会导致op1的逻辑变化

  • 就算逻辑不变化,修改op2也会导致重新编译和部署User1

我们通过下面这种方将不同的操作隔离成接口,运用第5节的LSP,我们将OPS类实现这三个接口,然后替换在User1中的U1Ops,由于依赖的是最小接口所以就不会出现上面的问题。

0.1.5  依赖反转原则-DIP

高层策略性的代码不应该依赖实现底层细节的代码,相反,那些实现底层细节的代码应该依赖高层策略性的代码。

0.2 设计模式的分类

0.2.1 创建型设计模式

关注对象的创建机制。创建新对象可能导致项目复杂性增加,该类型的模式旨在通过控制创建过程来解决这种问题。

如:单例(Singleton)、工厂(Factory)、建造者(Builder)、原型(Prototype)、构造器(Contructor)

在以类为中心的面向对象编程语言中,类和对象的关系可以想象成铸模和铸件的关系,对象总是从类中创建而来。而在原型编程的思想中,类并不是必需的,对象未必需要从类中创建而来,一个对象是通过克隆另一个对象所得到的。JavaScript就是属于原型对象编程。在JavaScript语言中,我们无需太注重很多创建型设计模式。

0.2.2 结构型设计模式

关注对象的组成与依赖关系。

如:代理(Proxy)、组合(Composite)、享元(Flyweight)、装饰器(Decorator)、适配器(Adapter)

0.2.3 行为设计模式

关注不同对象之间的通信。

如:观察者(Observer)、发布/订阅(Publish/Subscribe)、策略(Strategy)、迭代器(Iterator)、命令(Command)、模板方法(Tempalte)、职责链(Chain of Responsibility)、中介者(Mediator)、状态(State)

一、创建型设计模式

1.1 单例模式

定义

单例模式的核心是确保一个类只能有一个实例,即使多次实例化该类,也只返回第一次的实例化对象。单例模式不仅能减少不必要的内存开销, 并且在减少全局的函数和变量冲突也具有重要的意义。

ES6实现

class Singleton {
  constructor (name) {
    if (!Singleton.instance) {
      this.name = name
      Singleton.instance = this
    }
    return Singleton.instance
  }

  getName () {
    console.log(this.name)
  }
}

const a = new Singleton('第一次实例化')
const b = new Singleton('第二次实例化')

a.getName() //输出:第一次实例化
b.getName() //输出:第一次实例化

console.log(a===b) //输出:true
View Code

1.2 工厂模式

定义

把对象的创建放到一个工厂类中,通过参数来创建不同的对象。

场景

假设有一个做汽车整车组装的代加工工厂“福是康”,它帮各大汽车品牌加工不同型号的汽车。我们看它可以帮哪些品牌生产哪些型号的汽车。

简单工厂模式实现

const CarFactory = function (brand) {
  function Product (options) {
    this.brand = options.brand
    this.models = options.models
  }

  switch (brand) {
    case 'BMW':
      return new Product ({
        brand: '宝马',
        models: ['750', 'X5', 'Mini']
      })
      break;
    case 'benz':
      return new Product ({
        brand: '奔驰',
        models: ['S600', 'GLS', 'smart']
      })
      break;
    case 'audi':
      return new Product({
        brand: '奥迪',
        models: ['A8', 'Q7', 'R8']
      })
    default:
      throw new Error('参数错误,我们只生产:BMW/benz/audi')
      break;
  }
}

const BMWModels = new CarFactory('BMW')
const benzModels = new CarFactory('benz')
const audiModels = new CarFactory('audi')
View Code

工厂方法模式实现

const CarFactory = function (brand) {
  if (this instanceof CarFactory) {
    if (!this[brand]) throw new Error('参数错误,我们只生产:BMW/benz/audi')
    return new this[brand]()
  } else {
    return new CarFactory(brand)
  }
}

CarFactory.prototype = {
  BMW: function () {
    this.brand = '宝马',
    this.models = ['750', 'X5', 'Mini']
  },
  benz: function () {
    this.brand = '奔驰',
    this.models = ['S600', 'GLS', 'smart']
  },
  audi: function () {
    this.brand = '奥迪',
    this.models = ['A8', 'Q7', 'R8']
  }
}

const BMWModels = CarFactory('BMW')
const benzModels = CarFactory('benz')
const audiModels = CarFactory('audi')
View Code

抽象工厂模式实现

二、结构型设计模式

三、行为设计模式

3.1 策略模式

定义

定义若干个算法,把它们各自封装成独立的策略方法,这些策略方法具有相同的目标和意图;当有计算需求时,将需求委托给某一个或多个策略方法去执行。

场景1

公司根据员工的当前工资和绩效打分来计算年终奖。例如,绩效为S的人年终奖是工资的4倍,绩效为A的人年终奖是工资的3倍。

原始代码实现

var calculateBonus = function (performanceLevel, salary) {
  if (performanceLevel === 'S') return salary * 4
  
  if (performanceLevel === 'A') return salary * 3

  if (performanceLevel === 'B') return salary * 2
}

calculateBonus('S', 2000) // 8000
calculateBonus('A', 1000) // 3000
View Code

策略模式实现-面向对象版本

var performanceS = function () {}
performanceS.prototype.calculate = function (salary) {
  return salary * 4
}

var performanceA = function () {}
performanceA.prototype.calculate = function (salary) {
  return salary * 3
}

var performanceB = function () {}
performanceB.prototype.calculate = function (salary) {
  return salary * 2
}

var Bonus = function () {
  this.salary = null
  this.strategy = null
}

Bonus.prototype.setSalary = function (salary) {
  this.salary = salary
}

Bonus.prototype.setStrategy = function (strategy) {
  this.strategy = strategy
}

Bonus.prototype.get = function () {
  return this.strategy.calculate(this.salary)
}

// 应用
var bonus = new Bonus()
bonus.setSalary(1000)
bonus.setStrategy(new performanceS())
bonus.get() // 4000

bonus.setStrategy(new performanceA())
bonus.get() // 3000
View Code

策略模式实现-JavaScript版本

// 普通版
var strategies = {
  "S": function (salary) {
    return salary * 4
  },
  "A": function (salary) {
    return salary * 3
  },
  "B": function (salary) {
    return salary * 2
  }
} 

var calculateBonus = function (level, salary) {
  return strategies[level](salary)
}

calculateBonus('S', 2000) // 8000
calculateBonus('A', 1000) // 3000

// 高阶函数版
var S = function (salary) {
  return salary * 4
}

var A = function (salary) {
  return salary * 3
}

var B = function (salary) {
  return salary * 2
}

var calculateBonus = function (strategyFun, salary) {
  return strategyFun(salary)
}

calculateBonus(S, 1000) // 4000
View Code

场景2

给某个文本框添加多种校验规则

<html>

<body>
    <form action="http:// xxx.com/register" id="registerForm" method="post">
        请输入用户名:<input type="text" name="userName" />
        请输入密码:<input type="text" name="password" />
        请输入手机号码:<input type="text" name="phoneNumber" />
        <button>提交</button>
    </form>
    <script>
        /***********************策略对象**************************/
        var strategies = {
            isNonEmpty: function (value, errorMsg) {
                if (value === '') {
                    return errorMsg;
                }
            },
            minLength: function (value, length, errorMsg) {
                if (value.length < length) {
                    return errorMsg;
                }
            },
            isMobile: function (value, errorMsg) {
                if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
                    return errorMsg;
                }
            }
        };
        /***********************Validator 类**************************/
        var Validator = function () {
            this.cache = [];
        };
        Validator.prototype.add = function (dom, rules) {
            var self = this;
            for (var i = 0, rule; rule = rules[i++];) {
                (function (rule) {
                    var strategyAry = rule.strategy.split(':');
                    var errorMsg = rule.errorMsg;
                    self.cache.push(function () {
                        var strategy = strategyAry.shift();
                        strategyAry.unshift(dom.value);
                        strategyAry.push(errorMsg);
                        return strategies[strategy].apply(dom, strategyAry);
                    });
                })(rule)
            }
        };
        Validator.prototype.start = function () {
            for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
                var errorMsg = validatorFunc();
                if (errorMsg) {
                    return errorMsg;
                }
            }
        };
        /***********************客户调用代码**************************/
        var registerForm = document.getElementById('registerForm');
        var validataFunc = function () {
            var validator = new Validator();
            validator.add(registerForm.userName, [{
                strategy: 'isNonEmpty',
                errorMsg: '用户名不能为空'
            }, {
                strategy: 'minLength:6',
                errorMsg: '用户名长度不能小于10 位'
            }]);
            validator.add(registerForm.password, [{
                strategy: 'minLength:6',
                errorMsg: '密码长度不能小于6 位'
            }]);
            var errorMsg = validator.start();
            return errorMsg;
        }
        registerForm.onsubmit = function () {
            var errorMsg = validataFunc();
            if (errorMsg) {
                alert(errorMsg);
                return false;
            }
        };
    </script>
</body>

</html>
View Code

3.2 状态模式

定义

把事物的每种状态封装成独立的类,跟此种状态有关的行为都封装在这个类的内部。将请求委托给当前的状态对象,当对象内部状态改变时,会带来不同的变化。

 状态模式和策略模式比较

相同点

它们都有一些状态方法或者策略方法,都有一个上下文,上下文把请求委托给这些方法来执行。

不同点

策略模式中,各种策略类之间是平等又平行的,使用者必须熟知这些策略,然后通过参数自主地调用某个策略方法,甚至自由组合某些策略方法来产生作用。

状态模式中,各种状态之间的切换关系是内部定义好的,使用者只需触发某个状态即可,不需操心行为的编排。

场景

状态切换的场景非常多,比如切换某个dom元素的class,切换音视频播放状态。我们以切换电灯的开关为例,按一下打开弱光,再按一下打开强光,再按一下关灯。

原始代码实现

<html>
<body>
  <script>
    var Light = function () {
      this.state = 'off'
      this.button = null
    }

    Light.prototype.init = function () {
      var button = document.createElement('button')
      var that = this

      button.innerHTML = '开关'
      this.button = document.body.appendChild(button)
      this.button.onclick = function () {
        that.buttonPressed()
      }
    }

    Light.prototype.buttonPressed = function () {
      if(this.state === 'off') {
        console.log('弱光')
        this.state = 'weakLight'
      } else if (this.state === 'weakLight') {
        console.log('强光')
        this.state = 'strongLight'
      } else if (this.state === 'strongLight') {
        console.log('关灯')
        this.state = 'off'
      }
    }

    var light = new Light()
    light.init()
  </script>
</body>
</html>
View Code

状态模式-面向对象版本

<html>
<body>
<script>
  // Light调度类
  var Light = function () {
    this.offState = new OffState(this)
    this.weakLightState = new WeakLightState(this)
    this.strongLightState = new StrongLightState(this)

    this.button = null
  }
  //  Light调度类的初始化方法
  Light.prototype.init = function () {
    var button = document.createElement( 'button' )
    var self = this
    this.button = document.body.appendChild( button )
    this.button.innerHTML = '开关'
    
    this.currentState = this.offState

    this.button.onclick = function () {
      self.currentState.buttonPressed()
    }
  }
  // Light调度类设置当前状态的方法
  Light.prototype.setState = function (newState) {
    this.currentState = newState
  }

  // 关闭状态类
  var OffState = function (light) {
    this.light = light
  }
  OffState.prototype.buttonPressed = function () {
    console.log('弱光')
    this.light.setState(this.light.weakLightState)
  }
  // 弱光状态类
  var WeakLightState = function (light) {
    this.light = light
  }
  WeakLightState.prototype.buttonPressed = function () {
    console.log('强光')
    this.light.setState(this.light.strongLightState)
  }

  // 强光状态类
  var StrongLightState = function (light) {
    this.light = light
  }
  StrongLightState.prototype.buttonPressed = function () {
    console.log('关闭')
    this.light.setState(this.light.offState)
  }

  // 调用
  var light = new Light()
  light.init()
</script>
</body>
</html>
View Code

状态模式-JavaScript版本

<html>
<body>
<script>
  // 将客户的操作委托给delegation对象
  var delegate = function (client, delegation) {
    return {
      buttonPressed: function () {
        return delegation.buttonPressed.apply(client, arguments)
      }
    }   
  }

  // 状态机
  var FSM = {
    off: {
      buttonPressed: function () {
        console.log('关灯')
        this.button.innerHTML = '下一次按会打开弱光'
        this.currentState = this.weakLightState
      }
    },
    weak: {
      buttonPressed: function () {
        console.log('弱光')
        this.button.innerHTML = '下一次按会打开强光'
        this.currentState = this.strongLightState
      }
    },
    strong: {
      buttonPressed: function () {
        console.log('强光')
        this.button.innerHTML = '下一次按会关灯'
        this.currentState = this.offState
      }
    }
  }

  // Light调度类
  var Light = function () {
    this.offState = delegate(this, FSM.off)
    this.weakLightState = delegate(this, FSM.weak)
    this.strongLightState = delegate(this, FSM.strong)
    this.currentState = this.offState
    this.button = null
  }
  //  Light调度类的初始化方法
  Light.prototype.init = function () {
    var button = document.createElement( 'button' )
    var self = this
    button.innerHTML = '已关灯'
    this.button = document.body.appendChild( button )
    this.button.onclick = function () {
      self.currentState.buttonPressed()
    }
  }

  // 调用
  var light = new Light()
  light.init()
</script>
</body>
</html>
View Code

 

参考:

https://refactoringguru.cn/design-patterns/catalog

posted on 2019-03-10 08:28  dawnxuuu  阅读(189)  评论(0编辑  收藏  举报

导航