这是系列文章

  • 设计模式 - 理论(本文)
  • 设计模式 - JDK
  • 设计模式 - Spring

避免陷阱

以前也学习过设计模式,而且不止一次,都以失败告终。究其原因:

一是教材选用不对——《大话设计模式》,虽然这本书豆瓣评分8.3,但显然不适合我。讲的太啰嗦,我会讨厌弱智似的故事情节:什么大鸟小鸟老鸟、王二狗林蛋大,读者都是技术人员,作者却还是用小学生作为的语气讲故事,亦或是,难道大家真的都习惯了这种口吻接受知识?

二来,技术书籍也好,网络文章也好,好像都是一个模子刻出来的,上来就是UML图、代码实现,模式解决了什么问题一笔带过。读者注意力转移不说,还很容易流于形式:更多考虑的是这个模式为什么要定义这个接口?而不是什么情况下、为什么使用这个模式?——后者搞清楚了,与模式规定的接口不一样也没有关系。

三是,实践不够多,代码量不够,看到的代码也不够——时机不成熟。

每本设计模式的教材都教我们,不要轻易使用设计模式,只有需要时才使用,以避免过度设计。我觉得这是一个误解,在甚至搞不清楚三个工厂模式具体区别时,弄懂设计模式才是关键。至于使用,我认为该多用,直到熟悉之后,再考虑用得合不合适。

前段时间Github Daily推荐了《给人看的设计模式》,没有UML图,短小精悍,再次触发了我学习设计模式的兴趣。

这是一篇总结性的文章,视图用一句话描述一个设计模式存在的价值

设计模式分类

创造类

创造类设计模式,主要关注对创建对象的方式,根据不同的创造需求产生了对应的设计模式。

  • 当创建一个对象有一定的逻辑复杂度,我们不希望用户直接接触到创建过程,就有了简单工厂模式,封装创建过程
  • 当创建一个对象有一定的逻辑复杂度,但创建过程会依据对象的实现而不同,就有了工厂方法模式,定义工厂抽象类,具体创建逻辑留在工厂子类实现,这在SPI经常使用
  • 当需要创建一族而不是一个相互关联的对象时,就有了抽象工厂模式
  • 当创建一个对象可能需要多个步骤,且步骤之间可以自由组合,就有了建造器模式,它解决了构造方法地狱的问题
  • 当一个对象的创建是直接来源于同类的其它对象时,就有了原型模式
  • 当需要控制一个类在整个系统仅有一个对象时,就有了单例模式

结构类

结构类设计模式,主要关注对象之间的组合方式,即对象之间如何相互作用。关注的是对象之间的关系

  • 当一个类需要使用另一个类,但类之间接口不兼容时,就有了适配器模式,为“另一个类”创建适配器,使其变得兼容

  • 当一个类中的某个特性发生变化,使用继承表达这种变化会引发子类泛滥时,就有了桥接模式,将变化的特性抽出,任其自身变化,再“桥接”回原类。

    这是一个使用组合替换继承的典型场景

  • 当多个对象本身的逻辑关系为树状结构时,我们自然地通过组合的方式将他们联系在一起,用户只需要获取根对象就能操作整套对象,这就是组合模式

  • 当需要为一个类动态增加一系列功能,而又不改变原类,且增加的功能要灵活增减,就有了装饰器模式

  • 当需要为一个功能复杂的类提供一套使用简单的访问接口时,就有了外观模式

  • 当系统中有大量相同对象,为了减少内容消耗,我们让这些对象共用一个存储位置,就有了享元模式

  • 当需要在调用对象时增加额外的控制逻辑,或者因某些原因不方便直接调用该对象,就有了代理模式

行为类

行为类设计模式关注的是如何在对象之间进行通讯,即如何在软件组件中运行行为

  • 当对同一个源对象,我们有多套处理方案,或多套处理逻辑,此时可以使用责任链模式,将每个方案或逻辑抽象为一个责任对象,他们共同构成责任链,源对象从责任链的这头走到那头,依次应用每一个逻辑

  • 将行为封装在对象中,然后传递给执行者,这就是命令模式,它将执行者和行为解耦。想象操作系统是执行者,各种shell命令就是被封装的行为对象。

  • 当我们需要在不暴露底层实现的情况下,提供一个简单的接口访问容器元素,这就是迭代器模式

  • 当两个或多个对象需要相互交流时,就有了中介者模式,中介者定义了这些类交流的行为,提供了一个平台。

  • 当需要永久保留程序运行状态,并提供日后恢复可能性时,就有了备忘录模式,它提供了回滚的能力

  • 当一个对象需要对另一个对象的状态变化做出响应时,就有了观察者模式,这个就太常用了

  • 当要对一个对象添加更多操作,又不想修改他们时,就有了访问者模式,将添加的操作集中到访问者

    被访问者需要提供接收访问者的接口;访问者一般访问的是被访问者的实现类

  • 当需要将行为的算法抽象出来,在运行时候动态动态应用不同的算法抽象,就有了策略模式,典型的如比较器

  • 当对象的行为需要根据“状态”变化,就有了状态设计模式,它允许我们通过修改“状态”来修改对象的行为

  • 当对象的行为步骤固定,但步骤的具体实现要放到子类中实现,就有了模板方法模式,他允许我们先定义算法框架,后定义算法实现

自问自答

三个工厂模式如何区分

  • 简单工厂:静态方法,只是封装了单个对象的创建过程
  • 工厂方法:非静态方法,对象创建的具体过程需要等到子类实现
  • 抽象工厂:非静态方法,提供的是多个创建方法,创建一组关联的对象

当抽象工厂的创建方法只有一个时,退化为工厂方法;当工厂方法的实现类和抽象类变成一个,且方法变成静态时,退化为简单工厂

命令模式与策略模式区分

疑惑点:都是将行为抽象出来

不同点:前者可能会包含命令接收者的状态信息,相对来说,它更加完整,增加一个环境就能运行了;后者只是一个纯粹算法的抽象

迭代器模式和外观模式区分

疑惑点:都隐藏了底层实现,通过简单接口暴露

不同点:从隐藏底层暴露简单接口这一点看,后者是包含前者的,因为前者仅针对容器类对象;但后者在实现上,是单独创建一个类对原类进行封装;而前者往往是一个可迭代接口

中介者模式和适配器模式区分

疑惑点:都是为两个对象提供“沟通渠道”

不同点:其实二者完全不同,前者是真的提供沟通渠道;后者只是在接口形式上使得二者兼容

访问者模式和装饰器模式区分

疑惑点:都是不修改原对象的情况下添加了更多操作

不同点:后者是真的完全不改变原有对象,通过创建新类将原类进行包装,在新类中添加功能,新类除了方法调用外,是访问不到原类内部的;前者并非无痛,访问者为了能够访问到被访问者,需要被访问者暴露方法将访问者注入。

且装饰器模式重点在”添加“功能;访问者模式重点在”访问“,正因为是”访问“,才需要将访问者注入被访问者。

总结

本文没有代码,没有UML图,只有使用场景和容易搞混的设计模式区分。适合已经了解设计模式的人加深印象,要想详细了解,还是十分建议去看《给人看的设计模式》。此外,强调几个点:

  • 忘记UML图,忘记UML图,忘记UML图,重要的事情说三遍。UML图只是形式上的定义,不用想肯定记不住,重点在于理解场景和实现思路。只要思路正确,和标准UML图差一点也没关系,并且,我们实现的代码最终一定会靠向该模式的标准结构。
  • 不要一个一个看,全部看完并理解,才能有对比。
  • 很多设计模式看起来比较相似,实际上他们确实有共通点,此时就需要自己体会了,不大好讲通。

留言

2021-10-04

⬆︎TOP