在工作中,大家可能常常会发现,很多人将 Java 设计模式、大学里学的高等数学和编译原理等,视为脱离实际工作的知识。
但不管是面试还是实际工作中,是不是经常听到大牛前辈们给“洗脑”,说设计模式很重要?为此可能还专门买书或参加培训学习过,可往往都因为应用效果不佳,从开始的很重视,到后来慢慢地习惯性忽略,最后在你心中它的重要程度可能还远不如明天要交差的需求。
工作多年后,回过头再来看,其实,当时这些困惑的关键并不在于设计模式过于抽象或应用有难度,而在于可能从一开始就没有搞清楚设计模式的应用范围和背景:设计模式到底解决什么问题?为什么要抽象这样的场景?又是如何解决这些问题的?
一、对设计模式的常见误解
正因为没有搞清楚这些应用范围和背景,才导致大多数时候我们总是在“生搬硬套”设计模式,以为在应用设计模式,却不知道还没入门就一直在误解设计模式,并无法控制地胡乱使用,最后反而引入了很多不必要的麻烦。
因此,要想学好设计模式,就得摘去这些误解。
1、误解一:经典模式太抽象,很难学下去
说到设计模式,大家的第一反应是不是会想起“四人帮”GoF 的那本“经典”著作《设计模式:可复用面向对象软件的基础》?或者想起那 23 个“经典”的模式?
的确,设计模式太过于经典了,但是经典也会带来一个问题:过于抽象,难以快速理解。而对于业余时间本就不多的程序员来说,读抽象的经典更是一件难上加难的事。
比如说,下面是《设计模式》一书中关于访问者模式使用场景的描述:
一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor 使得你可以将相关的操作集中起来定义在一个类中。 当该对象结构被很多应用共享时,用 Visitor 模式让每个应用仅包含需要用到的操作。 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
大家会发现,即便很认真地阅读完,也看得似懂非懂,看上去似乎能和实际场景有一些结合,但真到了实际的设计和编码中时,却发现无从下手。为什么呢?
- 首先,针对访问者模式使用场景,这一段描述太长了,你阅读完得花上几分钟,然后再想几分钟,并且还掺杂了一些别的概念,查阅资料又得花费几分钟甚至几小时。
- 其次,表述太抽象。其中关于操作“污染”、不相关操作、不同的接口、改变对象的结构等,不同的人可能定义完全不同,就会导致在不同的真实场景中应用时千差万别。
- 最后,23 个设计模式都有类似这样的使用场景描述。要想保证团队中不同的人,对这样抽象的描述保持高度统一的认知,并且记住大致场景,还得理解差距不大,这必定很耗费沟通和学习时间。
最为关键的是,当我们面对真实的业务需求时,如何才能快速选择最合适的设计模式?这个问题依然没有被解答。
其实,经典之所以太抽象,是因为包含的知识密度太高,需要花时间解读。 比如说,在实际业务中,我们其实更关心:设计模式到底如何落地实践?不同的设计模式之间有哪些共性和独特性?有没有真实的代码示例?等等。也就是说,我们需要先搞清楚设计模式能解决哪些范围的问题后,才能正确使用设计模式。
实际上,在设计模式提出之初就已经说明,也就是《设计模式》一书的副标题:可复用面向对象软件的基础。设计模式解决的是“可复用”的设计问题,而类似可靠性设计、性能设计、安全性设计、可服务性设计等都不是设计模式能够解决的。
简单来说,设计模式从不同项目中总结出来的通用经验,是为了帮助我们快速理解现有的系统,并从中找出共性规律,如果没有足够的经验或者思考,反而容易引入错误的设计,造成更多的麻烦。
所以说,“经典太抽象”只是一个事实,只要能肯花时间认真解读,学下去并不难。
2、误解二:设计模式太单一,复杂业务场景难落地
现在,对于设计模式,有两个非常有意思的现象。
- 在理论学习中,几乎所有的开发人员都认为它很重要。比如,目前国内大多数的技术面试都会考查设计模式的相关知识点;再比如,在实际工作中,越来越多的程序员们或多或少都会在自己的项目中引入开源框架,由于开源框架中都会用到设计模式,渐渐地设计模式又被大家重新拿出来学习。
- 在工作实践中,绝大部分开发人员在项目中又找不到合适的应用场景。比如,如何设计一个安全权限控制系统?如何设计容灾方案?从实际场景来看,设计模式似乎只能解决技术层面上的问题,业务上完全派不上用场。
其实,发生这个冲突的关键点在于:没有搞清楚设计模式解决问题的范围所在。换句话说,设计模式并不是一种全场景的解决方案,它需要考虑适用范围。
比如,在面向对象语言 Java 领域中,如何最大限度发挥面向对象语言的继承与组合的威力?如何解耦程序的相互依赖?设计模式会提供一些解答。
如果说现在接到的是一个复杂系统的设计任务,比如,如何设计一个秒杀系统?那么我们不仅需要关心业务功能的实现,还要关心不同开发成员间的相互配置、服务器资源等,而此时脑海中浮现设计模式中的适配器模式、策略模式、状态模式……对此时的我们来说帮助并不那么明显,因为其实还没有到如此需要细节实现的阶段。
实际上,设计模式的提出就是为了解决限定领域的有限问题。 比如,针对非业务场景的技术框架,如何实现可复用的软件?如何能够为更多的人提高编程效率?像 Spring、Netty、MyBatis、JDK 等大家公认的工具,其实随处可见设计模式的应用,但同时并不是只有设计模式本身。
所以说,我们不能把设计模式当作一种通用解决方案来对待,或者认为它就应该解决超出范围的问题,一定要考虑好它的适用范围,否则问题是得不到有效解决的。
3、误解三:模式既然很好用,那么一切皆模式
比如,大家可能会说:
- 设计模式既然是能直接套用到某种场景的解决办法,不应该现实场景中应用很少;
- 设计模式既然是能用来解决面向对象设计的理论模型,不应该性能设计、安全设计用不上;
- 设计模式既然是统一的沟通语言,不应该有人还不知道或不喜欢;
- ...
上面这些其实就是“愿望思维”,而且是逻辑有误的。
Java 设计模式原本是从不同的编程项目中总结出来的通用经验,是现存的既定事实,能解决很多工具、组件、框架复用的问题。比如说,大家经常使用的 Spring 框架里,从工厂模式、原型模式,到代理模式、门面模式,再到观察者模式等,在其源码中随处可见。
于是,很多人就认为编程时应该处处使用模式,并且用得模式越多,设计就越好。事实上,好的设计从来不是看用的模式有多少,而是看如何合理利用模式的设计思想,以及如何利用模式解决真实的问题。
比如,在设计一个网关系统时,就想着该如何使用各种模式,即便是一个小的过滤规则模块,也在想着能不能使用责任链模式,而实际上那个需求只需要简单的白名单就能解决。最后的结果当然就是代码变得更复杂、更难以维护,并导致大量重构和沟通工作涌现。
所以说,学习设计模式是为了启发我们的思考,而不是“手里握着锤子,满世界找钉子” 。
二、如何正确学习设计模式?
难道设计模式真的不好用?或者根本已经成为过去式?其实争论的焦点不应该在于设计模式本身是否有用,而是我们到底有没有掌握正确的学习方法和应用设计模式的思维模式。
那该如何让经典真正为我所用呢?有以下四个很简单的方法。
首先,要摆正心态。 设计模式不是万能灵药,不是银弹,设计模式能解决的问题其实是有限的,应该始终保持一个平常的心态,正确分析设计模式可以解决和不能解决的问题。不过,模式解决的问题虽然是有限的,但能激发想象和灵感,从中获取更多有用的信息来解决更多的问题。总之,放弃争论设计模式的有用性,花更多的时间去学习和实践,不要总想着不思考直接使用。
其次,搞清楚设计模式的背景知识。 比如,设计模式如何定义?设计模式的历史演进与变化?设计模式有哪些适用的领域?又有哪些不适用的领域?如何结合实践分析和使用?学习一门知识时,如果总是忽略关联的背景知识,久而久之会养成零碎知识积累的习惯——收藏了很多资料,拆解、吸收却很少。而在学习关联知识时,就会发现,原来的知识会逐渐连接和串联起来,这是一个事半功倍的动作。
再次,努力具备高手独立思考的习惯。互联网时代,不缺资料和方法,缺的是能解决复杂难题的高手。高手之所以成为高手,是因为高手不拘泥于某一知识的高低贵贱,而是保持明智的判断,始终朝着坚定的目标前行。大家的身边一定有这样的隐藏高手,他们在面对复杂难题时,总是能保持清晰的头脑,谦虚并具备独立思考的习惯,我们应该多多向他们学习。同样,还需要花时间去刻意练习,在失败和挫折中探索真实的能力长什么样,并借此不断强化自己的能力。
最后,从现在开始,坚持。 学习技术知识,不比看文学小说,需要面对更为枯燥的业务场景,有时还需要反复做一些自己可能不太喜欢的重复工作。但只有坚持才能成长,才能锤炼出属于自己的真实能力。大多数时候我们都更容易半途而废,可是要想突破瓶颈,提升能力,升职加薪,如果没有付出与努力,那么当机会真的到来时,很可能会抓不住。因此,一定不要中途放弃,一定要坚持住。