关于设计模式五大原则

什么是设计模式

设计模式是一种应对多场景或者复杂的业务场景的一种工具,有利于代码的复用/维护,系统模块的组织和设计的沟通

设计模式五大原则

开闭原则

开闭原则(OCP: open closed principle) - 对拓展开放、对修改关闭
开闭原则的设计思路是:在我们系统已有的场景下,对于需要拓展的功能进行开放,拒绝直接的功能修改
exm:
假如现在有PUBG和LOL两个游戏,我们需要在LOL弹出一个充值折扣的功能,PUBG需要一个高亮的功能(功能随意,只是为了demo的理解而写)
首先看一个一下就就能想到的万能if写法

if(game === 'PUBG') {
    
    
  // 高亮
} else {
    
    
  // ……
}

// event
if(game === 'LOL') {
    
    
  // 弹出折扣框
} else {
    
    
  // 付款
}

这种写法虽然很好理解,但对于代码维护和可复用性来说无疑是非常差的,那么针对于开闭原则,可以做以下改造

class Game {
    
    //基础类 进行基础方法定义
    constructor (name) {
    
    
        this.name = name;
    }

    setColor(){
    
    
        console.log('color');
    }

    openDialog(){
    
    
        console.log('付款');
    }
}

class LOL extends Game {
    
    
    openDialog(){
    
    
        //我需要基础的方法的部分功能,但有一些又不能满足我的需求,于是我给他重写了
        console.log('折扣');
    }
}

class PUBG extends Game {
    
    
    openDialog(){
    
    
        console.log('付款');
    }
    setColor(){
    
    
        console.log('高亮');
    }
}

class DNF extends Game {
    
    
    /**
     * 假如以后我又有一款游戏,这个游戏只需要基础的模式即可,那么我只需要创造出来
     * 就自带了基础的功能,如果我想要添加新的功能,直接在这个游戏类里面进行添加/修改
     */
}

这个例子就说明了OCP的设计思想,对于一个系统/模块,在很多情况下存在同样功能的时候,统一管理核心功能模块并进行维护即可,对于个别派生出来的模块需要有别的功能,添加/修改即可,不会影响到核心

单一职责原则

单一职责原则(SRP) - 通过解耦让每一个职责更加的独立,职责单一,互不重叠
exm: 要在游戏里面有一个弹框的功能,以下是基础类

class PUBGManager {
    
    
  openDialog() {
    
    
    // 弹框
    // 计算金额
    setPrice();
  }
}

const game = new PUBGManager();
game.openDialog(); // 弹框 < = > 计算金额 两个模块耦合

很明显,这样的写法达到功能需求是可以的,但是如果到了code review的时候,不难发现,弹框和计算金额两个模块耦合了,那么如何进行重构/优化呢?

// game库 (前置功能,拿到游戏的管理器)
function gameManager(game) {
    
    
  return `${
      
      game}Manager`;
}

// gameManager.js - 游戏管理器
class PUBGManager {
    
    
    //通过命令修改价格
    constructor(command){
    
    
    //command可以理解为后续拿到的金额设置模块的一个实例
    //也是一种设计模式(命令模式),我的操作对象不是元素/模块本身,而是命令
        this.command = command;
    }
    openDialog(price){
    
    
        this.command.setPrice(price);
    }
}

// optManager.js - 底层库
class PriceManager {
    
    
    setPrice(price){
    
    
        //配置金额
    }
}

// main.js
const exe = new PriceManager();
const game = new PUBGManager(exe);
game.openDialog(15);
// game.setPrice(10); // 若需求需要直接调用可增加该模块

这样做看起来就没有那么耦合了,因为我把功能抽离了来,游戏也成了统一的管理,每一个文件管理一个大类,而每一个模块就做专一的一个小事,从而达到了解耦的目的

依赖倒置原则

依赖倒置原则(DIP):上层不应依赖底层实现,也就是我们的设计应该依赖于接口的抽象,而不依赖于具体的实现
exm 需要一个分享功能,老规矩,先来一个反面教材

// 分享功能
class Store {
    
    
  constructor() {
    
    
    this.share = new Share();
  }
}

class Share {
    
    
  shareTo() {
    
    
    // 分享到不同平台
  }
}

const store = new Store();
store.share.shareTo('wx');

//这个时候需要增加一个功能
// 评分功能
class Store {
    
    
  constructor() {
    
    
    this.share = new Share();
    this.rate = new Rate();//问题就在这里
  }
}

class Share {
    
    
  shareTo() {
    
    
    // 分享到不同平台
  }
}

class Rate {
    
    
  star(stars) {
    
    
    // 评分
  }
}

const store = new Store();
store.rate.stars('5');

这样固然是实现了功能,那么假如我现在有一个大的版本迭代呢,需求要加很多功能模块,那我就需要在constructor里面去新增挂载,显然,这样的设计是不好的,那么正面教材又来了

// 目标: 暴露挂载 => 动态挂载
class Rate {
    
    
  init(store) {
    
    
    store.rate = this;
  }
  store(stars) {
    
    
    // 评分
  }
}

class Store {
    
    
  // 维护模块名单
  static modules = new Map();

  constructor() {
    
    
    // 遍历名单做初始化挂载
    for (let module of Store.modules.values()) {
    
    
      module.init(this);
    }
  }

  // 注入功能模块
  static inject(module) {
    
    
    Store.modules.set(module.constructor.name, module);
  }
}

class Share {
    
    
  init(store) {
    
    
    store.share = this;
  }
  shareTo(platform) {
    
    
    // 分享到不同平台
  }
}

// 依次注册完所有模块
const rate = new Rate();
Store.inject(rate);

// 初始化商城
const store = new Store();
store.rate.star(4);

这样就实现了一个动态挂载的功能,实例在新建的时候就已经有了所需的功能,有了功能其实只需要调用游戏库里面注入的功能的方法,就可以把模块挂载到游戏库里面了

接口隔离原则

假设一个场景,有一个接口有我们需要的某个或者某几个功能,但是我们每次run都会跑接口所有的功能模块,这样的接口一般称为胖接口,那么接口隔离原则即是对接口进行分组管理,拆分模块
接口隔离原则(ISP):多个专业的接口比单个胖接口好用
exm: 现在我已经可以开发游戏了,但是需要实现游戏中台 - 快速生产游戏

class Game {
    
    
  constructor(name) {
    
    
    this.name = name;
  }
  run() {
    
    
    // 跑
  }
  shot() {
    
    
    // 开枪
  }
  mega() {
    
    
    // 开大
  }
}

class PUGB extends Game {
    
    
  constructor() {
    
    
    // pubg constructor
  }
}

class LOL extends Game {
    
    
  constructor() {
    
    
    // lol constructor
  }
}

pubg = new PUBG('pubg');
pubg.run();
pubg.shot();
pubg.mega();

那么这个例子一看就看出毛病了:谁家的PUBG还能开大?那么接下来就是改造环节了

// 用多个接口替代他,每个接口服务于一个子模块
// 瘦身
class Game {
    
    
  constructor(name) {
    
    
    this.name = name;
  }
  run() {
    
    
    // 跑
  }
}

class FPS {
    
    

}

class MOBA {
    
    

}

class PUGB extends Game {
    
    
  constructor() {
    
    
    // pubg constructor
  }
  shot() {
    
    }
}

class LOL extends Game {
    
    
  constructor() {
    
    
    // lol constructor
  }
  mega() {
    
    }
}

那么这样的话就实现了每个接口/功能模块专注于做自己的事情

里氏替换原则

里氏替换原则(LSP:the Lxxxx substitution principle):子类能够覆盖父类,父类能够出现的地方子类就能出现,子类可以扩展,不能改变
exm: 现在就是要把游戏分为pc端和mobile端来做

class Game {
    
    
  start() {
    
    
    // 开机逻辑
  }
  shutdown() {
    
    
    // 关机
  }
  play() {
    
    
    // 游戏
  }
}

const game = new Game();
game.play();

// sprint 2
class MobileGame extends Game {
    
    
  tombStone() {
    
    
    // tombStone
  }
  play() {
    
    
    // 移动端游戏
    //这里的play覆盖掉了父类的play
  }
}
const mobile = new MobileGame();
mobile.play();

问题来了MobileGame 的play实际上覆盖掉了父类的play,这样直接去覆盖属性其实是不好的,那么下面就是基于里氏替换原则的改进

class Game {
    
    
  start() {
    
    
    // 开机逻辑
    console.log('start');
  }
  shutdown() {
    
    
    // 关机
    console.log('shutdown');
  }
}

class MobileGame extends Game {
    
    
  tombStone() {
    
    
    console.log('tombStone');
  }
  play() {
    
    
    console.log('playMobileGame');
  }
}

class PC extends Game {
    
    
  speed() {
    
    
    console.log('speed');
  }
  play() {
    
    
    console.log('playPCGAME');
  }
}

不难发现,父类已经不再具有play的方法了,我把play下发到子类去进行自我实现,这样也就体现出了“子类能够覆盖父类,父类能够出现的地方子类就能出现”的功能,里氏替换原则更多的关注的是代码的可维护性,可长期迭代性

最后

以上就是设计模式的五大原则,下机

猜你喜欢

转载自blog.csdn.net/weixin_48391468/article/details/121890433
今日推荐