常用设计模式
You can walk as far as you want.
海阔凭鱼跃,天高任鸟飞。
更新
2019年12月3日 享元模式和代理模式
2019年12月4日 观察者模式
引言
问:什么是设计模式?
答:设计模式(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();
享元模式
定义
享元模式是以共享的方式高效地支持大量的细粒度对象。通过复用内存中已存在的对象,降低系统创建对象实例的性能消耗。
ps:一般是解决系统性能问题的,所以经常用于底层开发,在项目开发中并不常用。
举一个比较典型的例子:
现在这样两家服装店,他们均有50件男装,50件女装,现在要进行摆拍,推销,出售。
常规思路
其中A店主是这么干的,他批发了石膏模特,男女各50个。然后穿上衣服,进行摆拍。转成代码如下:
class Gyp {
constructor (index) {
this.index = index;
}
takePhotos(index) {
console.log(`石膏${this.index}号,穿着第${index}件衣服拍照`);
}
}
for (let i = 0; i < 100; i++) {
const gyp = new Gyp(i + 1);
gyp.takePhotos(i + 1);
}
使用享元模式解决问题
B店主这时候发话了,你这不是钱烧得慌么,我去雇俩真人模特,男女各一个,不就完了吗?转成代码如下:
class Model {
constructor (sex) {
this.sex = sex;
}
takePhotos(index) {
console.log(`${this.sex}模特,穿着第${index}件衣服拍照`);
}
}
// run 'node ./flyweight.js'
const maleModel = new Model('男');
const femaleModel = new Model('女');
for (let i = 0; i < 50; i++) {
maleModel.takePhotos(i + 1);
}
for (let i = 0; i < 50; i++) {
femaleModel.takePhotos(i + 1);
}
从例子中很明显可以看出享元模式的好处(当然,现实中也没有像A店主这么蠢的...)
总结
享元模式是一个优化重复、缓慢和低效数据共享代码的经典结构化解决方案。
代理模式
定义
简单一句话概括,就是为其他对象提供一种代理以控制对这个对象的访问。
代理模式的目的是为了解耦,当一个对象不适合或者不能直接引用另一个对象时,而代理对象可以在对象之间起到中介的作用。
优点
- 能将代理对象与真实对象被调用的目标对象分离
- 一定程度上降低耦合度,扩展性好
- 保护并增强目标对象
缺点
- 因为多增加一个代理对象,会造成请求处理速度变慢
- 会造成系统设计中类的数目的增加
- 增加了复杂度
举个栗子
分析:真实对象是某国首脑(需要重点保护对象),代理对象是秘书(增加成本)。目标对象是楼下奶茶店。然后结合代理模式的优缺点看,一目了然。
class Leader {
constructor(name) {
this.name = name;
console.log(`${name}是一名首脑`);
}
shopping() {
const proxyObj = new ProxySecretary('B');
proxyObj.proxyBehavior(this.name);
}
}
class ProxySecretary {
constructor(name) {
this.name = name;
console.log(`${name}是一名秘书`);
}
proxyBehavior(targetName){
console.log(`${this.name}代替${targetName}去买一杯奶茶`);
}
}
测试代码及结果
const leader = new Leader('A');
leader.shopping();
观察者模式
定义
观察者模式定义一系列对象之间的一对多关系,当一个对象改变、更新状态时,依赖它的都会收到通知改变或者更新。它又被称为 发布-订阅模式。
使用场景
当一个对象的状态发生改变,需要通知到其他对象(各位观察者们),大大了降低了对象与对象之间的耦合度。
举个例子
最典型的例子就是比如各位看官老爷关注了我,当我更新该动态时,各位看官老爷们将第一手收到掘金推送的卡片(我发的文章)。此时的关系是一(我)对多(关注我的看官老爷们)。
声明一个廉价的作者
class Subject {
constructor() {
this.observers = [];
}
/**
* 订阅事件
* @param {*} observer
*/
on(observers) {
observers.forEach(observer => {
if (this.observers.some(item => item.name === observer.name)) {
console.log(`${observer.name}已关注了作者,无需再关注啦...`);
}else {
this.observers.push(observer);
console.log(`${observer.name}关注了作者`);
}
});
}
/**
* 发布事件
*/
publish(message) {
this.observers.forEach(item => {
item.listener(message);
});
}
}
声明一个看官老爷
class God {
constructor(name) {
this.name = name;
}
listener(message) {
console.log(`作者给${this.name}推送了一条消息,内容如下:${message}`);
}
}
测试代码及结果
const subject = new Subject();
const godA = new God('A')
const godB = new God('B');
const godC = new God('C');
subject.on([godA, godB, godC, godA]);
subject.publish('请大佬们多多支持!');
设计模式之间的关系
思考
有时候比实现更重要的,是我们的思想,要学会思考问题,善于反思反问。语言只是实现的工具,最终成长的是我们的个人能力。fighting~
end
您的点赞和关注是对我最大的支持,谢谢!
推荐阅读
-
计算机 毕业设计 Python 深度学习 房价预测 房源可视化 房源爬虫 二手房可视化 二手房爬虫 递归决策树模型 机器学习 深度学习 大数据 毕业设计
-
桥接模式的解释和代码实现
-
[C 语言教程] [嵌入式程序设计] (I) 简介和先决条件 (II) 嵌入式程序设计基础 (III) 硬件基础 (IV) 硬件寄存器操作
-
设计并实施基于 SpringBoot 的 DIY 计算机安装教程网站。
-
系统架构设计器教程 第 19 章 19.4 Kappa 架构说明
-
Spring Boot 视频网站:技术选择与架构设计
-
滚雪球式的 Redis [第 9.2 讲]:Redis 的最佳实践:高效应用和常见反模式规避指南
-
代理模式、BigDecimal 解释
-
Unity 常用组件介绍
-
Linux 安装和部署服务:nginx和Openresty - IV,nginx常用配置