23种设计模式的优点与缺点概况

时间:2022-07-12 21:15:21

设计模式

标签(空格分隔): 设计模式优点 应用场景


整理自《设计模式之禅》

单例模式

优点:

  • 只有一个实例,减少了内存开支;
  • 可以避免对系统资源的多重占用;
  • 可以在系统中设置全局的访问点,优化和共享资源访问;

缺点:

  • 没有接口,扩展困难;
  • 对测试开发不利;

应用场景:

  • 要求生成唯一序列号的场景;
  • 需要一个共享访问点;
  • 创建一个对象需要消耗过多的资源时
  • 需要定义大量的静态常量和静态方法时(也可直接声明为static的方式);

工厂方法模式

优点:

  • 良好的封装性,代码结构清晰;
  • 扩展非常好;
  • 屏蔽产品类;

应用场景:

  • 是new一个对象的替代品;
  • 需要灵活的,可扩展的框架时;
  • 使用在测试驱动开发的框架下;

抽象工厂模式

优点:

  • 封装性;
  • 产品族内部的约束为非公开状态;

缺点:

  • 产品族扩展困难;

模板方法模式

优点:

  • 封装不变部分,扩展可变部分,把不变的算法封装到父类实现,可变的部分则通过继承来扩展;
  • 提取公共部分代码,便于维护;
  • 行为由父类控制,子类实现;

缺点:

  • 子类对父类产生影响,子类执行的结果影响了父类的结果;

应用场景:

  • 多个子类有公有的方法,且逻辑相同时;
  • 重要,复杂的算法,可以把核心算法设计为模板方法;
  • 重构时,把相同的代码抽取到父类,然后通过钩子函数结束其行为;

建造者模式

优点:

  • 封装性,使得客户端不必知道产品内部的组成细节,我们不用关心每一个具体的模型内部是如何实现的。
  • 建造者独立,容易扩展
  • 便于控制细节风险,由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响;

建造者模式的应用场景:

  • 相同的方法,不同的执行顺序,会产生不同的结果时;
  • 多个部件或零件,都可以装配到一个对象中,但产生的运行结果又不相同时,如Android中的AlertDialog的构造;
  • 产品类非常复杂,或产品类的的调用顺序不同产生不同的效果;

代理模式

优点:

  • 职责清晰,其实的角色就是实现实际的业务的逻辑,不用关心其他非本职责的事务;
  • 高扩展性,具体主题角色随时都会发生变化,但只要它实现了接口,我们的代理类就可以在完全不做任何修改的情况下使用;

原型模式(通过实现Cloneable接口)

优点:

  • 性能优良,原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好,特别是要在循环体内产生大量对象时,
  • 避免构造函数的约束,直接是在内存中拷贝的,构造函数是不会执行的。

应用场景:

  • 类初始化需要消化非常多的资源时
  • 性能和安全要求的场景,通过 new产生一个对象需要非常繁琐的数据准备和访问权限时;
  • 一个对象多个修改者的场景,一个对象需要提供给多个对象访问,而且各个调用者都可以修改其值时;

注意地方:浅拷贝与深拷贝

Java的Object类提供的clone方法只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,其他的原始类型如int,char等都会被拷贝,拷贝后的对象与原生对象共享内部元素的地址(浅拷贝),如果拷贝后的对象修改了原生对象的数组,则原生对象也会看到修改。如果需要进行深拷贝,则需要在复写的clone方法里对私有的类变量(内部数组,引用对象)进行独立的拷贝。并且使用final关键字修饰的变量不能被拷贝;

中介者模式

优点:

  • 减少了类间的依赖,把原有的一对多的依赖变成了一对一的依赖;

    缺点:

  • 中介者会膨胀得很大,而且逻辑复杂;原本N个对象的依赖关系转换为中介者与对象的依赖关系;

命令模式

优点:

  • 类间解耦,调用者与接收者之间没有任何依赖关系,调用者实现功能时不需要了解到底是哪个接收者执行,只需调用Command抽象类的execute方法就可以了;
  • 可扩展性,Command的子类可以非常容易扩展,并且调用者和高层模块不产生严重的代码耦合;

缺点:

  • Command类膨胀厉害,如果有N个命令,则Command类的子类就为N个;

    应用场景:如Android中各种事件的处理;

责任链模式

优点:

  • 请求与处理分开,请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌;

缺点:

  • 性能问题,每个请求都是从链头遍历到链尾的,当这个责任链比较长时,遍历开销会比较大;

应用场景:

  • 如Android事件的传递机制;

装饰器模式

优点:

  • 装饰类和被装饰类可以独立发展,而不会互相耦合;
  • 装饰模式是继承关系的一个替代方案,不管装饰多少层,最终返回的也还是那个对象;
  • 装饰模式可以动态地扩展一个实现类的功能;

缺点:

  • 多层的装饰比较复杂,当使用多层装饰出现问题时,排查问题的工作量比较大

应用场景:

  • 需要扩展一个类的功能,或给一个类增加附加功能;
  • 需要为一批兄弟类进行改装或加装功能;

策略模式

优点:

  • 算法可以*切换,只要实现抽象策略,它就成为策略家庭的一个成员;
  • 避免使用多重条件判断,
  • 扩展性良好,在现有的系统中增加一个策略太容易,只要实现接口就可以了;

缺点:

  • 策略类数量多,每一个策略都是一个类,复用的可能性很小;
  • 所有的策略类都需要对外暴露,上层模块必须知道有哪些策略,然后决定使用哪一个策略;

应用场景:

  • 多个类只有在算法或行为上稍有不同的场景;
  • 算法需要*切换的场景;
  • 需要屏蔽算法规则的场景;

适配器模式

优点:

  • 让两个没有任何联系的类在一起运行;
  • 增加了类的透明性;
  • 提高了类的复用度;
  • 灵活性好,当不需要适配器时,只要删掉这个适配器就可以了,

应用场景:

  • 修改一个已经投产的接口时,
  • Android中各种Adapter,

迭代器模式

  • 迭代器模式是为解决遍历容器中的元素而诞生的,没有人会单独写一个迭代器,使用Java提供的Itreator就可以满足要求了;

组合模式

优点:

  • 高层模块调用简单,高层模块不需要关心自己处理的是单个对象还是整个组合结构,
  • 节点可以*增加;

缺点:

  • 调用时会直接使用实现类,不符合面向接口编程思想;

应用场景:

  • 维护和展示部分-整体关系的场景,如树型菜单,文件和文件夹的管理;
  • 只要是树型结构,就要考虑使用组合模式;

观察者模式

优点:

  • 观察者与被观察者之间是抽象耦合,不管是增加观察者还是被观察者都非常容易扩展;
  • 建立一套触发机制;

缺点:

  • 一个被观察者,多个观察者,开发与调度会比较复杂,在Java中消息的通知默认是顺序执行,其中一个观察者卡壳,会影响整体的执行效率,一般要考虑采用异步的方式;

应用场景:

  • 关联行为场景,如Android中数据变化会引起UI的变化;
  • 事件多级触发场景;
  • 跨系统的消息交换场景;

门面模式

优点:

  • 减少系统的相互依赖,所有的依赖都是与门面对象的依赖,与子系统无关。
  • 提高了灵活性;
  • 提高了安全性,想让你访问子系统的哪些业务就开通哪些逻辑;

缺点:

  • 不符合开闭原则,当出现bug后,只能通过修改门面角色的代码来修复;

应用场景:

  • 为一个复杂的模块或子系统提供一个供外界访问的接口,如Android的Context类只是一个抽象类,所有的功能都是在ContextImpl类实现的,我们不会察觉到ContextImpl的存在,只需要调用Context就可以了;
  • 子系统相对独立,外界对子系统的访问只要黑箱操作即可;
  • 预防低水平开发人员带来的风险,被限定在指定的子系统开发;

备忘录模式

应用场景:

  • 需要保存和恢复数据的相关状态场景;
  • 提供一个可回滚的操作场景;
  • 需要监控的副本场景中;
  • 数据库连接的事务管理就是用的备忘录模式;

注意事项:

  • 备忘录的生命期,要主动管理它的生命周期,建立就要使用,不使用就删除;
  • 备忘录的性能,不要在频繁建立备份的场景中使用备忘录模式;(对象的创建是需要消耗资源的)

访问者模式

优点:

  • 符合单一职责原则,具体元素角色负责数据的加载,而访问者类则负责数据的呈现;
  • 优秀的扩展性,
  • 灵活性非常高;

缺点:

  • 具体元素对访问者公布细节,访问者要访问一个类就必须要求这个类公布一些方法和数据;
  • 具体元素变更比较困难;具体元素角色的增加、删除、修改都是比较困难;
  • 违背了依赖倒置原则,访问者依赖的是具体的元素,而不是抽象的元素;

应用场景 :

  • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作;
  • 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作”污染“这些对象的类;
  • 业务规则要求遍历多个不同的对象;

状态模式

优点:

  • 结构清晰,避免了过多的switch...caseif...else语句的使用;
  • 遵循设计原则,每个状态就是一个子类;
  • 封装性非常好,将状态变换放置到类的内部来实现;

缺点:

  • 子类会太多,也就是类膨胀,有多少个状态,就会有多少个子类;

    应用场景:

  • 行为随状态改变而改变的场景,如权限设计;
  • 条件、分支判断语句的替代者,通过扩展子类实现条件的判断处理;
  • 状态的个数最好不要超过5个;

解释器模式(现在使用较少)

优点:

  • 扩展性好,

缺点:

  • 解释器模式会引起类膨胀;
  • 采用了递归调用方法;

享元模式

优点:

  • 大大减少应用程序创建的对象,降低程序内存的占用;

缺点:

  • 提高了系统复杂性,需要分离出内部和外部状态;

应用场景:

  • 系统中存在大量的相似对象;
  • 需要缓冲池的场景;
  • 细粒度的对象都具有较接近的外部状态;且内部状态与环境无关

桥梁模式

优点:

  • 抽象与实现分离;
  • 优秀的扩充能力;
  • 实现细节对客户透明;

应用场景:

  • 不希望或不适用继承的场景;
  • 接口或抽象类不稳定的情况;
  • 重要性要求较高的场景;