欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

常用设计模式

最编程 2024-06-01 11:22:21
...
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'));


单例模式

单例模式老生常谈了,顾名思义就是一个类只有一个实例。

特点

  1. 确保自己只有一个实例。
  2. 必须自己创建自己的实例。
  3. 必须为其他对象提供唯一的实例。

优点

  1. 节省内存,提高访问速度
  2. 更加灵活更改类的实例化过程
  3. 避免全局使用的类频繁地创建与销毁
  4. 避免对资源的多重占用(比如写文件操作)

缺点

  1. 与单一职责原则冲突(一个类应该只关心内部逻辑,而不关心外面怎么样来实例化)
  2. 不适用于变化的对象

ps:单一职责原则就是一个类/接口/方法只负责一项职责或职能

实现方式 

  1. 饿汉单例 (顾名思义,还未使用到该类,但是先实例化)
  2. 懒汉单例(顾名思义,用到时再实例化)

饿汉单例

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();


工厂模式

实现方法

  1. 简单工厂模式
  2. 工厂方法模式 (实现子类继承父类,不适用js)
  3. 抽象工厂模式 (不直接生成实例, 而是用于对产品类簇的创建)

因为后面两种工厂模式使用较少,这里以简单工厂模式为例。简单工厂模式又叫静态工厂模式,由一个工厂对象决定创建某一种产品对象类的实例。主要用来创建同一类对象。它主要适用于创建的对象数量较少,对象的创建逻辑不复杂时使用。说白了,它就是把你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店主这么蠢的...)

总结

享元模式是一个优化重复、缓慢和低效数据共享代码的经典结构化解决方案。

代理模式

定义

简单一句话概括,就是为其他对象提供一种代理以控制对这个对象的访问。

代理模式的目的是为了解耦,当一个对象不适合或者不能直接引用另一个对象时,而代理对象可以在对象之间起到中介的作用。

优点

  1. 能将代理对象与真实对象被调用的目标对象分离
  2. 一定程度上降低耦合度,扩展性好
  3. 保护并增强目标对象

缺点

  1. 因为多增加一个代理对象,会造成请求处理速度变慢 
  2. 会造成系统设计中类的数目的增加
  3. 增加了复杂度

举个栗子

 假如你是一名某国首脑,你想喝一杯咖啡,你亲自买显然不现实、不安全。于是你委托你的秘书代你去买一杯奶茶,这个过程就是代理模式。

分析:真实对象是某国首脑(需要重点保护对象),代理对象是秘书(增加成本)。目标对象是楼下奶茶店。然后结合代理模式的优缺点看,一目了然。

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


您的点赞和关注是对我最大的支持,谢谢!