设计模式

55 阅读14分钟

构造器模式

// var employee1 = {
//   name: 'kervin',
//   age: 100
// }
// var employee2 = {
//   name: 'tiechui',
//   age: 18
// }
// 上面这种创建有共同特点的对象,如果需要创建很多,代码就会很臃肿,用构造器模式会变得简洁
// 构造函数
function Employee(name,age) {
  this.name = name;
  this.age = age;
  this.say = function() {
    console.log(this.name + "-" + this.age)
  }
}
var employee1 = new Employee('kervin', 100);
var employee2 = new Employee('tiechui', 18);
console.log(employee1)
employee1.say();

原型模式

// 构造模式,没创建一个对象会开辟一个新的内存,每个对象都有相同的say方法,如果创建成千上万个这样的对象,say方法也会创建很多次,
// 形成了浪费内存,没有很好的复用,接下来用原型模式,就可以把say提取出来,让所有这样的对象共用一个方法

// 原型模式(把方法挂在原型链上)
function Employee(name,age) {
  this.name = name;
  this.age = age;
}
Employee.prototype.say = function() {
  console.log(this.name + "-" + this.age)
}
var employee1 = new Employee('kervin', 100);
var employee2 = new Employee('tiechui', 18);
employee1.say();
employee2.say();
=======================es class的写法==========================
class Employee{
  constructor(name,age){
    this.name = name;
    this.age = age;
  }
  say() {
    console.log(this.name + "-" + this.age)
  }
}
var employee1 = new Employee('kervin', 100);
var employee2 = new Employee('tiechui', 18);
employee1.say();
employee2.say();

总结,运用构造器模式和原型模式可以把复杂的功能抽象出来,形成复用,可以用传参的方式来对每个对象进行差异化处理

工厂模式

简单工厂模式的优点是,只需要一个正确的参数就可以获取到想要的对象,而无需知道它创建的具体细节, 但是在函数内包含了所有对象的创建逻辑和判断逻辑代码,每增加新的构造函数,还需要修改判断逻辑代码, 当我们的对象不是3个而是10个,或更多时,这个函数会成为一个庞大的超级函数,变得难以维护,所以简单工厂函数只能用于创建的对象数量较少,对象的创建逻辑不复杂时使用

// 工厂模式----由一个工厂对象决定创建某一种产品对象类的实例,创建同一类对象
function UserFactory(role) {
  // 构造函数创建同一类对象
  function User(role,pages) {
    this.role = role;
    this.pages = pages;
  }
  switch(role) {
    case 'superadmin':
      return new User('superadmin', ['home', 'user-manage', 'right-manage', 'news-manage']);
      break;
    case 'admin':
      return new User('admin', ['home', 'user-manage', 'news-manage']);
      break;
    case 'editor':
      return new User('editor', ['home', 'news-manage']);
      break;
    default:
      throw new Error("参数错误");
  }
}
var user = new UserFactory('superadmin'); // 传入superadmin、admin、user即可创建不同角色的对象
=============es6==================
// 构造函数创建同一类对象
class User {
  constructor(role,pages){
    this.role = role;
    this.pages = pages;
  }
  // 工厂模式----由一个工厂对象决定创建某一种产品对象类的实例,创建同一类对象
  static UserFactory(role) {
    switch(role) {
      case 'superadmin':
        return new User('superadmin', ['home', 'user-manage', 'right-manage', 'news-manage']);
        break;
      case 'admin':
        return new User('admin', ['home', 'user-manage', 'news-manage']);
        break;
      case 'editor':
        return new User('editor', ['home', 'news-manage']);
        break;
      default:
        throw new Error("参数错误");
    }
  }
}
var user = User.UserFactory('superadmin'); // 传入superadmin、admin、user即可创建不同角色的对象

抽象工厂模式

抽象工厂模式并不直接生成实例,而是用于对产品类族的创建; 比之前简单工厂更加解耦了;

class User{
  constructor(name,role, pages) {
    this.name = name;
    this.role = role;
    this.pages = pages;
  }
  welcome() {
    console.log('欢迎回来', this.name);
  }
  dataShow() {
    // abstract
    throw new Error('抽象方法需要被实现')
  }
}
class SuperAdmin extends User{
  constructor(name) {
    super(name, 'superadmin', ['home', 'user-manage', 'right-manage', 'news-manage']);
  }
  // 重写dataShow
  dataShow() {
    console.log('superadmin-datashow');
  }
  addRight() {

  }
  addUser() {

  }
}
class Admin extends User{
  constructor(name) {
    super(name, 'admin', ['home', 'user-manage', 'news-manage']);
  }
  // 重写dataShow
  dataShow() {
    console.log('admin-datashow');
  }
  addUser() {
    
  }
}
class Editor extends User{
  constructor(name) {
    super(name, 'editor', ['home','news-manage']);
  }
  // 重写dataShow
  dataShow() {
    console.log('editor-datashow');
  }
  addUser() {
    
  }
}
// 抽象工厂
function getAbstactUserFactory(role) {
  switch(role) {
    case 'superadmin':
      return SuperAdmin;
      break;
    case 'admin':
      return Admin;
      break;
    case 'editor':
      return Editor;
      break;
    default:
      throw new Error("参数错误");
  }
}
let UserClass = getAbstactUserFactory('superadmin');

let user = new UserClass('kervin');
user.dataShow();

建造者模式

建造者模式属于(builder pattern)属于创建型模式的一种,提供一种创建复杂对象的方式。他将一个复杂的对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式是一步一步的创建一个复杂的对象,他允许用户只通过制定复杂的对象的类型和内容就可以构建他们,用户不需要制定内部的具体构造细节。

class Navbar{
  init() {
    console.log('navbar-init')
  }
  getData() {
    console.log('navbar-getdata')
  }
  render() {
    console.log('navbar-render')
  }
}
class List{
  init() {
    console.log('list-init')
  }
  getData() {
    console.log('list-getdata')
  }
  render() {
    console.log('list-render')
  }
}
// 建造者这个类关注的是具体一步步的过程
class Creator{
  async startBuild(builder) {
    await builder.init();
    await builder.getData();
    await builder.render();
  }
}
const op = new Creator();
op.startBuild(new Navbar());
op.startBuild(new List());

单例模式

1、保证一个类只有一个实例,并提供一个访问它的全局访问点;
2、主要解决一个全局使用的类平凡的创建和销毁,占用内存;

==============es5===================
// es5实现单例模式的原理是闭包
// 闭包:在函数内部返回函数,被外界变量引用
// 导致函数内部的变量无法被释放
var Singleton = (function() {
  var instance;
  // 类也可以写到外面
  function User(name,age) {
    this.name = name;
    this.age = age;
  }
  return function(name, age) {
    if(!instance) {
      instance = new User(name, age);
    }
    return instance;
  }
})();
console.log(Singleton() === Singleton()); // true

===================es6==================
class Singleton{
  constructor(name,age) {
    // 在类上挂一个属性,当做独一份的变量
    if(!Singleton.instance) {
      this.name = name;
      this.age = age;
      Singleton.instance = this;
    }
    return Singleton.instance;
  }
}
console.log(new Singleton('kervin', 100) === new Singleton('xiaoming', 18))

用单例模式实现一个登录框,每次打开保证只创建一次dom,避免重复创建销毁,浪费内存;

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .kervin{
      position: fixed;
      width: 200px;
      height: 200px;
      line-height: 200px;
      text-align: center;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background: yellow;
    }
  </style>
</head>
<body>
<!-- <div class="kervin">登录对话框</div> -->
<button id="open">打开</button>
<button id="close">关闭</button>
<script>
  var Model = (function() {
    var instance = null;
    return function() {
      if(!instance) {
        instance = document.createElement('div');
        instance.innerHTML = '登录对话框';
        instance.className = 'kervin';
        instance.style.display = 'none';
        document.body.appendChild(instance);
      }
      return instance;
    }
  })();
  document.querySelector('#open').onclick = function() {
    const modal = Model();
    modal.style.display = 'block';
  }
  document.querySelector('#close').onclick = function() {
    const modal = Model();
    modal.style.display = 'none';
  }
  
</script>
</body>
</html>

装饰器模式

装饰器模式能够很好的对已有功能进行拓展,这样不会更改原有的代码,对其他业务产生影响,这方便我们在较少的改动下对软件功能进行拓展。

Function.prototype.before = function(beforeFn) {
  var _this = this;
  console.log('before')
  return function() {
    // 先进行前置函数调用
    beforeFn.apply(this, arguments);
    // 再执行原来的函数
    return _this.apply(this, arguments);
  }
}
Function.prototype.after = function(afterFn) {
  var _this = this;
  console.log('after')
  return function() {
    // 执行完原来函数
    var result = _this.apply(this, arguments);
    // 在执行后置函数
    afterFn.apply(this, arguments);
    return result;
  }
}
// var test = new Function
function test() {
  console.log('原函数执行')
  return 11111;
}
var test2 = test.before(function() {
  console.log('前置函数')
}).after(function() {
  console.log('后置函数')
})
console.log('返回值', test2())

适配器模式

将一个类的接口转换成客户希望的另一个接口,适配器模式让那些接口不兼容的类一起工作;

class TencenMap{
  show() {
    console.log('开始渲染腾讯地图')
  }
}
class BaiduMap{
  display() {
    console.log('开始渲染百度地图')
  }
}
// 腾讯地图和百度地图的渲染方法名称不同,我们用一个适配器让他俩的的喧嚷方法名称相同
class TencenAdapater extends TencenMap{
  constructor() {
    super();
  }
  display() {
    this.show();
  }
}
function renderMap(map){
  map.display();
}
renderMap(new TencenAdapater())
renderMap(new BaiduMap())

策略模式

策略模式定义了一系列算法,并将每个算法封装起来,使他们可以互相替换,且算法的替换不会影响使用算法的客户,策略模式属于对象行为模式,他通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
该模式主要解决在有多重算法相似的情况下,使用if...else...所带来的复杂和难以维护,他的优点是算法可以自由切换,同时可以避免多重if...else判断,且具有良好的扩展性。

策略模式用映射表来代替if...else判断,代码会变得优雅简洁,扩展性更强

// 计算不同评级的年终奖
function calBonus(level, salary) {
  if(level === 'A') {
    return salary * 4;
  }
  if(level === 'B') {
    return salary * 3;
  }
  if(level === 'C') {
    return salary * 2;
  }
}

// 上面的代码很多if...else
// 将其改成策略模式
let strategry = {
  'A': (salary) => {
    return salary * 4;
  },
  'B': (salary) => {
    return salary * 3;
  },
  'C': (salary) => {
    return salary * 2;
  }
}
function calBonus(level, salary) {
  return strategry[level](salary);
}
calBonus('A', 10000);
calBonus('C', 10000);

代理模式

代理模式,为其它对象提供一种代理以控制对这个对象的访问。
代理模式使得代理对象控制具体对象的引用,代理几乎可以是任何对象:文件、资源、内存中的对象,或者是一些难以复制的东西。

class Star{
  play() {
    console.log('演戏')
  }
}
class StarProxy{
  constructor() {
    this.superStar = new Star();
  }
  talk(price) {
    if(price >= 10000) {
      this.superStar.play();
    } else {
      throw new Error('价钱不合适')
    }
  }
}
let jr = new StarProxy();
jr.talk(1000)
=============模仿vue的代理模式===========
var vueobj = {
 data: 0
}
let proxy = new Proxy(vueobj, {
  get(target, key) {
    return target[key]
  },
  set(target, key, value) {
    if(key === 'data') {
      console.log('设置data了', value)
      box.innerHTML = value;
    }
    target[key] = value;
  }
})
setInterval(() => {
  proxy.data += 1;
}, 1000);

观察者模式

观察者模式包括观察目标和观察者。
一个目标可以有任意数量的与之相依赖的观察者。
一旦观察目标的状态发生改变,所有的观察者都将得到通知.

当一个对象的状态发生改变时,所有依赖于他的对象都得到通知并被自动更新,解决了主体对象与观察者之间功能的耦合,即一个对象状态改变给其它对象通知的问题;

// 观察目标
class Subject{
  constructor() {
    this.observers = [];
  }
  add(observer) {
    this.observers.push(observer);
  }
  remove(observer) {
    this.observers = this.observers.filter(item => item !== observer);
  }
  notify() {
    this.observers.forEach(item => {
      item.update();
    })
  }
}
// 观察者
class Observer{
  constructor(name) {
    this.name = name;
  }
  update() {
    console.log('update', this.name)
  }
}
const subject = new Subject();
const observer1 = new Observer('kervin');
const observer2 = new Observer('tiechui');
subject.add(observer1);
subject.add(observer2);

setTimeout(() => {
  subject.notify();
}, 2000);

观察者模式优点:目标者和观察者的耦合度降低,专注自身功能逻辑,观察者被动接收更新,时间上解耦,实时接收目标者更新状态;
缺点:观察者模式虽然实现了对象间依赖关系的低耦合,但是不能对事件通知进行细分管控,如筛选通知,指定主体事件通知。

发布订阅模式

1、观察者和目标要互相知道;
2、发布者和订阅者不用互相知道,通过第三方实现调度,属于经过解耦合的观察者模式。

// publish 发布
// subscribe 订阅
const PubSub = {
  message:{},
  publish(type,data) {
    if(!this.message[type]) return;
    this.message[type].forEach(item => item(data));
  },
  subscribe(type, cb) {
    this.message[type] ? this.message[type].push(cb) : this.message[type] = [cb];
  },
  unsubscribe(type,cb) {
    if(!this.message[type]) return;
    if(!cb) {
      this.message[type]  = [];
    } else {
      this.message[type] = this.message[type].filter(item => item !== cb);
    }
  }
}
function testA(data) {
  console.log('testA', data)
}
function testB(data) {
  console.log('testB', data)
}
function testC(data) {
  console.log('testC', data)
}
PubSub.subscribe('A', testA);
PubSub.subscribe('A', testB);
PubSub.subscribe('C', testC);

setTimeout(() => {
  PubSub.unsubscribe('A', testB)
}, 1000);
setTimeout(() => {
  PubSub.publish('A', 1111)
  PubSub.publish('C', 222)
}, 2000);

模块模式

模块化模式最初定义为在传统软件工程中为类提供私有和公共封装的一种方法。
能够使一个单独对象拥有公共/私有的方法和变量,从而屏蔽来自全局作用域的特殊部分,这可以减少我们的函数命名与在页面中其它脚本区域内定义的函数名冲突的可能性。

============es5模块化(闭包)========== 1.js

// 闭包
var obj = (function() {
  var count = 0;
  // 返回一个方法或对象
  return {
    increse() {
      return ++count;
    },
    decrese() {
      return --count;
    }
  }
})();

================es6模块化============ 2.js

let count = 0;

function increace() {
  return ++count;
}

function decreace() {
  return --count;
}

// es6模块化导出
export default {
  increace,
  decreace
}

3.js

let count = 0;

function increace() {
  return ++count;
}

function decreace() {
  return --count;
}

// es6模块化导出
export {
  increace,
  decreace
}

在页面的导入方式

<!-- es5模块化(闭包) -->
<script src="./1.js"></script>
<script>
// 模块化(可以让对象有私有变量); es13可以在类属性前加#,代表这个属性是私有变量
console.log(obj)
</script>
<!-- es6模块化 -->
<script type="module">
  import myObj from './2.js';
  console.log(myObj.increace())
  console.log(myObj.decreace())
</script>
<script type="module">
  import { increace, decreace } from './3.js';
  console.log(increace())
  console.log(decreace())
</script>

桥接模式

桥接模式:将抽象部分和实现部分分离,使他们都可以独立的变化;
使用场景:一个类存在两个或多个独立变化的维度,且这两个维度都需要进行扩展;
优点:把抽象与实现隔离开,有助于独立的管理隔离部分;
缺点:每使用一个桥接元素都要增加一次函数调用,这对应用程序的性能会有一些负面影响----提高了系统的复杂程度;

// modal
// message
// toast

// bounce
// slide
// rotate
const Animations = {
  bounce: {
    show(ele) {
      console.log(ele, '弹跳显示')
    },
    hide(ele) {
      console.log(ele, '弹跳隐藏')
    }
  },
  slide: {
    show(ele) {
      console.log(ele, '滑动显示')
    },
    hide(ele) {
      console.log(ele, '滑动显示')
    }
  },
  rotate: {
    show(ele) {
      console.log(ele, '旋转显示')
    },
    hide(ele) {
      console.log(ele, '旋转显示')
    }
  }
}

function Toast(ele, animation) {
  this.ele = ele;
  this.animation = animation;
}
Toast.prototype.show = function() {
  // 抽象
  this.animation.show(this.ele);
}
Toast.prototype.hide = function() {
  // 抽象
  this.animation.hide(this.ele);
}

let toast1 = new Toast('div1', Animations.bounce);
toast1.show();

组合模式

组合模式在对象间形成树形结构;
组合模式中基本对象和组合对象被一致对待;
无需关心对象有多少层,调用时只需在根部进行调用;

const Folder = function(folder) {
  this.folder = folder;
  this.list = []; // 保存子文件夹或者是文件
}
Folder.prototype.add = function(res) {
  this.list.push(res);
}
Folder.prototype.scan = function() {
  console.log('扫描文件夹', this.folder);
  for(let i = 0; i < this.list.length; i++) {
    this.list[i].scan();
  }
}
const File = function(tile) {
  this.file = tile;
}
File.prototype.scan = function() {
  console.log('开始扫描文件', this.file);
}
// 根
let rootFolder = new Folder('root');
// 子文件夹
let htmlFolder = new Folder('html');
let cssFolder = new Folder('css');
let jsFolder = new Folder('js');
// 文件
let html4 = new File('html4');
let html5 = new File('html5');
let css2 = new File('css2');
let css3 = new File('css3');
let es5 = new File('es5');
let es6 = new File('es6');

rootFolder.add(htmlFolder);
rootFolder.add(cssFolder);
rootFolder.add(jsFolder);

htmlFolder.add(html4);
htmlFolder.add(html5);

cssFolder.add(css2);
cssFolder.add(css3);

jsFolder.add(es5);
jsFolder.add(es6);

rootFolder.scan();

命令模式

有时候需要像某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,需要一种松耦合的方式来设计程序,使得发送者和接收者能够消除彼此之间的耦合关系。
命令模式3种角色构成:
1.发布者invoker(发出命令、调用命令对象,不知道如何执行与谁执行)。
2.接受者receiver(提供对应接口处理请求,不知道谁发起请求)。
3.命令对象command(接收命令,调用接受者处理发布者的请求)。

class Receiver{
  // 接收类
  execute() {
    console.log('接收者执行请求');
  }
}
class Command{
  // 命令类
  constructor(receiver) {
    this.receiver = receiver;
  }
  execute() {
    console.log('命令对象=>告诉接收者如何处理');
    this.receiver.execute();
  }
}
class Invoker{
  // 发布类
  constructor(command) {
    this.command = command;
  }
  invoke() {
    console.log('发布请求');
    this.command.execute();
  }
}

const order = new Command(new Receiver());
const client = new Invoker(order);
client.invoke();

模板方法模式

模板方法模式由两部分组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法,以及封装子类中所有方法的执行顺序,子类通过执行这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。

// Container用来生成F
const Container = function(params) {
  // 抽象父类(把一类事情用理论描述清楚,而不实现具体方法)
  function F() {}
  // 抽象方法(子类继承后需要重写)
  F.prototype.init = function(){
    var list = this.getData();
    this.render(list);
  }
  F.prototype.getData = params.getData || function(){
    throw new Error('必须传入getData')
  }
  F.prototype.render = params.render || function(list){
    console.log('render', list)
  }
  return F;
}

let Myclass = Container({
  getData() {
    console.log('获取nowplaying')
    return [1,2,3];
  }
})
let class1 = new Myclass();
class1.init();

let Myclass2 = Container({
  getData() {
    console.log('获取comingsoon')
    return [4,5,6];
  }
})
let class2 = new Myclass2();
class2.init();

迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴漏该对象的内部表示,迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
1.为遍历不同数据结构的"集合"提供统一的接口。
2.能遍历访问"集合"数据中的项,不关心项的数据结构。

const kervinEach = function(arr, callback) {
  for(let i = 0; i < arr.length; i++) {
    callback(i, arr[i])
  }
}
kervinEach([1,2,3,4], function(key, value) {
  // console.log(key, value)
  const oli = document.createElement('li');
  oli.innerHTML = value;
  list.appendChild(oli)
})

// ES6 Iterator
// Array、String、Map、Set、arguments、NodeList
var arr = ['kervin', 'xiaoming', 'tiechui'];
console.log(arr)
for(let i of arr) {
  console.log(i)
}

let it = arr[Symbol.iterator]();
console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())

// 给Object类型自动以添加迭代器
var obj = {
  0: 'kervin',
  1: 18,
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
}

for(let i of obj) {
  console.log(i)
}

对象实现迭代器

var obj = {
  code: 200,
  name: 'kervin',
  list: ['kervin', 'xiaoming', 'tiechui'],
  [Symbol.iterator]: function() {
    let index = 0;
    return {
      next:() => {
        if(index < this.list.length) {
          return {
            value: this.list[index++],
            done: false
          }
        } else {
          return {
            value: this.list[index++],
            done: true
          }
        }
      }
    }
  }
}
let it = obj[Symbol.iterator]();
console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())

for(let i of obj) {
  console.log(i)
}

职责链模式

使多个对象都有机会处理请求,从而避免了请求的发送者与多个接受者直接的耦合关系,将这些接受者连接成一条链,顺着这条链传递该请求,直到找到能处理该请求的对象。

btn.onclick = function() {
  console.log(input.value)
  if(input.value.length === 0) {
    console.log('这里不能为空')
  } else {
    if(Number.isNaN(+input.value)) {
      console.log('这里必须是数字')
    } else {
      if(input.value.length < 6) {
        console.log('您的密码长度不够')
      } else {
        console.log('注册成功')
      }
    }
  }
}

改成职责链模式

btn.onclick = function() {
  checks.check()
}
function checkEmpty() {
  if(input.value.length === 0) {
    console.log('这里不能为空')
    return false;
  }
  return 'next';
}
function checkNumber() {
  if(Number.isNaN(+input.value)) {
    console.log('这里必须是数字')
    return false;
  }
  return 'next';
}
function checkLength() {
  if(input.value.length < 6) {
    console.log('您的密码长度不够')
    return false;
  }
  return 'next';
}
// 职责链类
class Chain{
  constructor(fn) {
    this.checkRule = fn;
    this.nextRule = null;
  }
  check() {
    this.checkRule() === 'next' ? this.nextRule.check() : null;
  }
  addRule(nextRule) {
    this.nextRule = new Chain(nextRule);
    return this.nextRule;
  }
  end() {
    this.nextRule = {
      check: () => 'end'
    }
  }
}
const checks = new Chain(checkEmpty);
checks
.addRule(checkNumber)
.addRule(checkLength)
.end();

优点:
1、符合单一职责,使一个方法都有一个职责
2、符合开放封闭原则,在需求增加时可以很方便的扩充新的职责。
3、使用时候不需要知道谁才是真正处理方法,减少大量if或switch语句
缺点:
1、团队成员需要对职责链存在共识,否则当看到一个方法莫名其妙返回一个next时会很奇怪。
2、出错时不好排查问题,因为不知道到底在哪个责任中出的错,需要从链头开始往后找。