设计模式

初级

MVC和MVVM的区别?

  • MVC是包括view视图层、controller控制层、model数据层。各部分之间的通信都是单向的。 View传送指令到ControllerController完成业务逻辑后,要求Model改变状态,Model将新的数据发送到View,用户得到反馈。 比如说,用户在输入框里输入一个字符,触发controller,controller修改model数据,最后model数据传递给view层进行渲染。
  • MVVM是包括view视图层、viewModel视图模型层、model数据层。各部分之间的通信都是双向的。 View和Model并没有直接联系,而是通过ViewModel传递,Model和ViewModel之间的交互是双向的。 比如说,用户在输入框里输入一个字符,同时就会修改model数据,同理,如果model数据发生改变,view也会做出相应的改变,这就是双向绑定。

讲一讲创建型模式?

共五种:

  • 工厂方法模式:由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建。 但将全部创建逻辑集中到了一个工厂类中,它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了,违反开闭原则(对扩展开放,对修改关闭); 产品类不断增多时候,对系统的维护和扩展也非常不利。
  • 抽象工厂模式:可能工厂也会越来越多,我们用一个抽象工厂来生成其他工厂,然后用其他工厂生产产品。 工厂类随着目标类数量一起爆炸增长,管理多个工厂是抽象工厂要做的事情。
  • 单例模式:单例模式可以分为两种:预加载(还没有使用该单例对象,该单例对象就已经被加载到内存了。会造成内存的浪费。但是比较快)和懒加载(用到该单例对象的时候再创建)。
  • 建造者模式(生成器模式):当一个产品太过复杂的时候,就需要一个建造者以构造该产品的各个部件并组装起来。优点是细节分离,降低代码耦合度,扩展性强。
  • 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。用于创建重复的对象,同时又能保证性能。

讲一讲结构型模式?

共七种:

  • 适配器模式:作为两个不兼容的接口之间的桥梁。例如一个数据传入一个函数但数据结构不对,可以造一个函数适配,就是适配器。主要就是解决接口不兼容问题,同时提高复用性,和扩展能力。 但也会增加一些代码复杂度。
  • 装饰器模式:将有新功能的新对象包裹在原对象上,从而拓展原对象的新功能。从而在为对象添加新的行为的同时遵循开闭原则,也提高了灵活性和可维护性。缺点是增加了一定的复杂性。
  • 代理模式:用新对象包裹在原对象上,实现对原对象输入输出的监听与修改。可以起到保护目标对象的作用。但也会造成数据传递变慢,增加了系统复杂度。
  • 外观模式:门面模式,为多个子系统对外提供一个共同的接口,从而降低请求复杂度,提高可维护性,降低耦合度;但外观对象强耦合所有子系统,外观对象出错就会引起阻塞全局的单点故障。
  • 桥接模式:将一个维度的代码抽出来作为一个独立的工具类,而不参与到子类的继承与派生中,防止指数式增长的子类派生。这样的工具类属性引用就成为了不同维度之间的桥梁。 虽然说桥接模式的定义是将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。但这里的抽象与编程语言中的抽象类无关。 它们并不是一回事。 “抽象”更类似于调用api,一个封装的接口,一个抽出来的独立的工具类。这样提高了扩展性和灵活性。 但是识别独立变化的维度困难。正确识别系统中两个独立变化的维度并不总是容易的。
  • 组合模式:如果一个对象有若干个相互独立的部分组成,那么就将这几个部分的代码相互隔离开,组成“整体-部分“结构,解耦,简化了代码逻辑,提高可维护性。 问题在于这几个部分往往会藕断丝连,硬用组合模式会引起不必要的信息传输成本。比较适合的比如说文件系统,菜单等等。
  • 享元模式:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。 但为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。

讲一讲行为模式?

行为型模式,共十一种:

  • 策略模式:用于管理不同策略或算法,要将不同策略或算法放到不同类中,避免耦合,在运行时根据需要选择不同的算法。
  • 模板方法模式:用在一个功能的完成需要经过一系列步骤,这些步骤是固定的,但是中间某些步骤具体行为是待定的,在不同的场景中行为不同。 此时就可以考虑在这里插入一个模版函数作为占位符,不同的场景对应不同的子类实现。
  • 观察者模式:事件订阅,订阅者向发布者注册接口,发布者想发布东西,就挨个接口调用一遍,通知所有订阅者,松耦合,扩展性强,但性能可能有问题,更新顺序不确定。
  • 迭代器模式:提供一个统一的迭代接口,从第一个迭代到最后一个,从而不用管底层是什么数据结构以及遍历细节。符合单一职责,开闭原则,但是如果是太简单的遍历,直接for循环可能会更方便。
  • 责任链模式:将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理返回, 或将其传递给链上的下个处理者。适合多个权限检查步骤。或者说dom的冒泡也算。 符合单一职责,开闭原则,但部分请求由于提前返回导致接下来的处理者无法处理。
  • 命令模式:将GUI与业务逻辑分离,GUI捕获用户行为,并将该命令传递给业务逻辑层,GUI层没有业务逻辑处理。因此关键是所有命令最好统一接口。 符合单一职责,开闭原则,但由于需要实现命令发送与接收,代码可能会更复杂,也有传输效率问题。
  • 备忘录模式:也叫快照模式,例如文本编辑器的撤销于重做,我们不应该从外部复制一份编辑器的状态,而应该让编辑器自己提供复制函数让外部来调用。自行生成的快照放在备忘录对象中。 符合解耦,单一职责,开闭原则,但是备忘录会消耗大量内存。
  • 状态模式:在一个对象的内部状态变化时改变其行为, 例如React或vue都是这样的。
  • 访问者模式:将算法与其所作用的对象分离开来,让这些对象成为纯数据对象,操作可以独立变化。当访问者用不同算法访问这些纯数据对象,将产生不同的结果。 问题是每次修改数据对象,所有的访问方法都需要重新修改,另外算法被抽出放到访问者那里,可能没有权限了。
  • 中介者模式:不同GUI组件之间可能会有互动,甚至相互影响,多个GUI组件之间的交互应该都统一让父GUI作为中介处理,这就是中介者模式。 符合单一职责和开闭原则,但父GUI会承载很多不属于它的方法。
  • 解释器模式:一般就是解析编程语言用的,例如解析sql,正则,自定义语法的语句,然后将其解析为一个对象。 一般不要自制,因为解析器往往需要编译原理,引入第三方包调用就行了。