面试必备-设计模式(导论)

116 阅读7分钟

我正在参加「掘金·启航计划」

什么是设计模式?

每次看到设计模式,大家都会以为是什么高大上的东西,难以驾驭,看了一眼就pass。其实设计模式是开发人员在开发过程中在面临一般问题时经过大量实验和错误所总结出来的一套解决方案。可以说设计模式就是我们开发当中的方法论,通过设计模式设计代码,提高了代码的复用性,可读性,可靠性。

设计模式的提出最早见于《设计模式:可复用面向对象软件的基础》这本书中,GoF在其中总共总结了23种常见的软件开发模式,这其中又可以分成三大类:创建型模式 ,结构型模式和行为型模式

无论是哪种设计模式,或多或少都是遵循了一个或者多个设计原则。

设计原则主要有六大原则,在将设计模式之前我们先详细了解下设计原则,毕竟如果说设计模式是代码的基石,那设计原则就是设计模式的基石,万丈高楼平地起,基础才是最关键的。

设计原则

1.Single Responsibility Principle 单一职责原则

一个类或者一个方法只完成一个单一功能。理论上类或者方法实现功能越单一,其复用性越高。但是在实际开发中,如果功能过于简单,我们也可以根据业务情况进行适当合并,过度设计是没有必要的。

许多设计模式都是遵循着**单一职责原则,**例如代理模式,装饰器模式,策略模式等。

这里我们以代理模式中的缓存代理为栗子。

interface Subject{
  getSource():Boolen
}


//实现获取资源的功能
class Source implements Subject{
      getSource(id){
       console.log('获取相关资源')
      }
}
//实现缓存的功能
class ProxySource implements Subject{
  private sourceMap
  constructor(source:Source){
    this.source = source
  }
  getSource(id:number){
    if(!this.sourceMap){
      this.sourceMap = new Map()
    }
    if(this.sourceMap.include(id)){
      return sourceMap.get(id)
    }else{
      const source = this.source.getSource(id)
      this.sourceMap.set(id,source)
      return source
  }   
}

这里资源类只实现获取资源,而代理类实现资源的缓存,如果命中缓存资源,则直接从资源池中返回,从而大大节省了开销。

这样做有什么好处呢?

第一,将下载和缓存两个功能进行解耦,使得逻辑更加清晰,方便后期的维护。

第二,具有更好的扩展性。如果后期要加日志代理等功能时候,可以新建日志代理类,从而完成下载后记录日志的功能。

2.Liskov Substitution Principle 里式替换原则

应用程序中任何父类对象出现的地方,我们都可以用其子类的对象来替换,并且可以保证原有程序的逻辑行为和正确性。

要满足上述条件首先子类必须实现所有父类的方法,其次子类不能覆盖父类的非抽象方法。

这个原则是面向对象设计的基本原则之一,我们前端大部分情况下并不会使用继承的方式去实现功能,这里我也只介绍到这里。

3.Dependence Inversion Principle 依赖倒转原则

针对接口编程,依赖于抽象而不依赖于具体。

这个原则是开闭原则的基础,我们在开发中先定义接口,然后根据接口去做具体实现。

同样的,对于我们前端同学一般都是基于实现编程,大概了解下就好。

4.Interface Segregation Principle 接口隔离原则

使用多个隔离的接口,比使用单个接口要好。一般是用来降低类之间的耦合。

这个同上,大概了解下就好。

5.Demeter Principle 迪米特法则

也叫作最小知识原则,即一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

这个在门面模式和中介者模式等中都有体现

这里我将以我们比较少听说的外观模式举例。

class Subsistem1{
    methods(){
        console.log("subsistem1")
    }
}
class Subsistem2{
    methods(){
        console.log("subsistem2")
    }
}

class Subsistem3{
    methods(){
        console.log("subsistem3")
    }
}

class Facade{
   private subsistem1 = new Subsistem1()
   private subsistem2 = new Subsistem2()
   private subsistem3 = new Subsistem3()
    methods(){
        this.subsistem1.methods()
        this.subsistem2.methods()
        this.subsistem3.methods()
    }
}

const sistem = new Facade()
sistem.methods()

我们要实现一个功能时,需要调取3个系统中的方法,这里通过将实现方法封装在一个facade类中的methods方法中。我们从而不需要知道methods方法的具体实现,直接调用facade的方法即可。

这种思想在我们封装一些类库的时候经常会用到,对于调用方只需要知道这个方法是干嘛的,直接使用,并不需要知道其实现细节。

6.Open Close Principle 开闭原则

对扩展开放,对修改关闭。这个最重要的原则我把它放在了最后面。

绝大多数的设计模式都是符合开闭原则的。对于程序设计中我们经常说高聚低耦,理想情况下我们在需求变更的时候,通过策略模式模板模式等设计模式将变化的部分进行封装。

这里以我项目中遇到的一个上传图片方的法为栗子:

const uploadImgByOss = (  file: any,fileName: string,disWatermark?: boolean,isFile?: boolean)=>{
    //获取oss token相关逻辑 这里直接忽略
    //主要是这块逻辑:如果是文件直接返回地址,如果不需要水印,返回压缩后的图片,否则返回有水印的图片
      return new Promise(async function (resolve, reject) {
    try {
      await client.put(fileName, file);
      const url = isFile ? fileName : disWatermark ? compressImgUrl(fileName) : getImgeUrlWithWatermark(fileName);
      resolve({ data: url, code: 200 });
    } catch (e) {
      Toast.show(`上传失败 ${e}`);
    }
  });
}

一开始的需求只是上传图片并返回图片地址,后期又要加上其他文件的上传,然后又是图片要加上水印。每次需求变更,都是直接在代码上进行更改,不符合开闭原则。

这里我们梳理下需求:将图片上传至阿里云,将返回的url进行处理,对非图片文件直接返回url,对图片文件有的要加上水印,有的要进行压缩处理。

那么我们通过策略模式将上述代码改进下

type GetUrlType = 'file'|'watermark'|'common'


const urlMap = new Map([
  ['file',(fileName)=>fileName],
  ['watermark',(fileName)=>getImgeUrlWithWatermark(fileName)],
  ['common',(fileName)=>compressImgUrl(fileName)]
])

const uploadImgByOss = ( file: any,fileName: string,GetUrl:GetUrlType)=>{
    //获取oss token相关逻辑 这里直接忽略
      return new Promise(async function (resolve, reject) {
    try {
      await client.put(fileName, file);
      //通过策略模式简化代码
      const url = urlMap.get(GetUrlType)
      resolve({ data: url, code: 200 });
    } catch (e) {
      Toast.show(`上传失败 ${e}`);
    }
  });
}

以后我们只通过修改urlMap这块的代码就可以完成返回数据的修改了。

6大设计模式就介绍到这里了,这个就像武功的心法,在平时开发中我们要注意心法的修炼,你的内功就会更深厚,写出的代码可维护性更高,其他看过你代码的人直呼666.

设计模式

上面说的是内功,现在再讲下外功修炼(这里只做整体介绍,后续的文章再进行展开讲解):

1.创建型模式

正如其字面意思,提供对象在创建时候的机制,增加已有代码的灵活性和可复用性。

总共包含工厂模式抽象工厂模式单例模式建造者模式原型模式5种模式。我们常见并使用的有单例模式原型模式工厂模式。

2.结构型模式

将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。

总共包含适配器模式桥接模式过滤器模式组合模式装饰器模式外观模式享元模式代理模式8种模式。我们常见并使用的有组合模式装饰器模式代理模式,有些冷门的有享元模式适配器模式外观模式。

3.行为型模式

负责对象间的高效沟通和职责委派。

总共包含责任链模式命令模式解释器模式迭代器模式中介者模式备忘录模式观察者模式状态模式策略模式模板方法模式访问者模式10种模式。我们常见并使用的有责任链模式命令模式中介者模式观察者模式策略模式状态模式。

设计模式(导论)的内容就到这里了,后面的文章我将从以创建型,结构型,行为型的模式依次展开介绍工作中常用的设计模式,希望同学们学完后能在实际工作使用,让你的代码逼格拉满!

最后,我想问下大家想看class版本的还是function版本的呢?欢迎大家留言讨论。