You can walk as far as you want.
海阔凭鱼跃,天高任鸟飞。
引言
问:什么是设计模式?
答:设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
网上还有一种更好的说法:
假设有一个空房间,我们要日复一日地往里 面放一些东西。最简单的办法当然是把这些东西 直接扔进去,但是时间久了,就会发现很难从这 个房子里找到自己想要的东西,要调整某几样东 西的位置也不容易。所以在房间里做一些柜子也 许是个更好的选择,虽然柜子会增加我们的成 本,但它可以在维护阶段为我们带来好处。使用 这些柜子存放东西的规则,或许就是一种模式。
它和语言无关,它表达的是一种思想。
这里以js为例分享了几种常用的设计模式,后续会有补充。
所有源码均已上传至github:传送门
策略模式(strategy)
在了解策略模式前先举个例子:
马上就要到年底了,很多公司的年终奖是根据员工的工资基数和年底绩效情况来发放的。
- 绩效为A的人年终奖有4倍工资
- 绩效为B的人年终奖有3倍工资
- 绩效为C的人年终奖是2倍工资。
- 绩效为D的人年终奖是1倍工资。
假设财务部要求我们提供一段代码,来方便他们计算员工的年终奖。
假设工资基数为1000.
let base = 1000;复制代码
常规思路
let calculate1 = function(salary,level) {
if(level === 'A') {
return salary * 4;
} else if(level === 'B') {
return salary * 3;
} else if(level === 'C') {
return salary * 2;
} else if(level === 'D') {
return salary * 1;
}
};复制代码
点评:虽然功能实现了,但是这代码明显包含了太多if-else,简直不忍直视。首先扩展性很差,后续如果再加个EFG或者是将绩效基数调整一下,需要直接改动该方法的内部实现,这明显违背了开闭原则,非常不友好;其次如果有别的地方也用到了类似计算绩效的方式,但是规则不一样,这不够通用。
ps:开闭原则简单讲就是对扩展开放,对修改关闭。
使用组合函数重构代码
let salaryA = salary => salary * 4;let salaryB = salary => salary * 3;let salaryC = salary => salary * 2;let salaryD = salary => salary * 1;let calculate2 = function(salary,level) { if(level === 'A') { return salaryA(salary); } else if(level === 'B') { return salaryB(salary); } else if(level === 'C') { return salaryC(salary); } else if(level === 'D') { return salaryD(salary); }};
复制代码
点评:将计算绩效的算法统统封装了起来,看起来相比常规思路有所改善,只是使计算绩效的方法更加通用 ,扩展性还是不行。
使用策略模式重构代码
let strategies = { 'A': salary => salary * 4, 'B': salary => salary * 3, 'C': salary => salary * 2, 'D': salary => salary * 1};let calculate3 = function(salary,level) { return strategies[level](salary);};复制代码
总结:策略模式指的是定义一系列的算法,并且把它们封装起来,但是策略模式不仅仅只封装算法,我们还可以对用来封装一系列的业务规则,只要这些业务规则目标一致,我们就可以使用策略模式来封装它们。
(同理表单校验,这里不做阐述)
测试代码及结果
console.log('resultA = ', calculate3(base,'A'));console.log('resultB = ', calculate3(base,'B'));console.log('resultC = ', calculate3(base,'C'));console.log('resultD = ', calculate1(base,'D'));复制代码
单例模式
单例模式老生常谈了,顾名思义就是一个类只有一个实例。
特点
- 确保自己只有一个实例。
- 必须自己创建自己的实例。
- 必须为其他对象提供唯一的实例。
优点
- 节省内存,提高访问速度
- 更加灵活更改类的实例化过程
- 避免全局使用的类频繁地创建与销毁
- 避免对资源的多重占用(比如写文件操作)
缺点
与单一职责原则冲突(一个类应该只关心内部逻辑,而不关心外面怎么样来实例化)
不适用于变化的对象
ps:单一职责原则就是一个类/接口/方法只负责一项职责或职能
实现方式
- 饿汉单例 (顾名思义,还未使用到该类,但是先实例化)
- 懒汉单例(顾名思义,用到时再实例化)
饿汉单例
let instance = null;class Printer { constructor() { console.log('我是一台打印机'); } static getInstance() { if (instance === null) { console.log('初始化打印机...'); instance = new Printer(); } return instance; } print(){ console.log('打印中......'); }}复制代码
测试代码及结果
var printer = Printer.getInstance();//多 new 一次printer = Printer.getInstance();printer.print();复制代码
复制代码
懒汉单例
let LazyInstance = function(fn) { let result = null; return function() { result || (result = fn.apply(this, arguments)); }};复制代码
测试代码及结果
let myPrinter = function () { console.log('初始化一台打印机...');};let lazyInstance = LazyInstance(myPrinter);lazyInstance();复制代码
工厂模式
实现方法
- 简单工厂模式
- 工厂方法模式 (实现子类继承父类,不适用js)
- 抽象工厂模式 (不直接生成实例, 而是用于对产品类簇的创建)
因为后面两种工厂模式使用较少,这里以简单工厂模式为例。简单工厂模式又叫静态工厂模式,由一个工厂对象决定创建某一种产品对象类的实例。主要用来创建同一类对象。它主要适用于创建的对象数量较少,对象的创建逻辑不复杂时使用。说白了,它就是把你new权限收回,由它来统一实例化。
简单工厂模式
class Animal { constructor (type) { this.type = type; } setName(name) { this.name = name; } // getName() { // return this.name; // } detail() { if (!this.name) { console.log(`First of all, let's give its a name. `); return; } console.log(`this is a ${this.type},its name is ${this.name}`); } static getInstance(type) { return new Animal(type); }}复制代码
测试代码及结果
let cat = Animal.getInstance('cat');cat.setName('tom');cat.detail();复制代码
未完待续....
end
您的点赞和关注是对我最大的支持,谢谢!