设计模式之美(导读)

为什么要学设计模式

如何分层、分模块?应该怎么划分类?每个类应该具有哪些属性、方法?怎么设计类之间的交互?该用继承还是组合?该使用接口还是抽象类?怎样做到解耦、高内聚低耦合?该用单例模式还是静态方法?用工厂模式创建对象还是直接new出来?如何避免引入设计模式提高扩展性的同时带来的降低可读性问题?……各种问题,一下子挤到了我面前。
优秀的开源项目、框架、中间件,代码量、类的个数都会比较多,类结构、类之间的关系极其复杂,常常调用来调用去。所以,为了保证代码的扩展性、灵活性、可维护性等,代码中会使用到很多设计模式、设计原则或者设计思想。如果你不懂这些设计模式、原则、思想,在看代码的时候,你可能就会琢磨不透作者的设计思路,对于一些很明显的设计思路,你可能要花费很多时间才能参悟。相反,如果你对设计模式、原则、思想非常了解,一眼就能参透作者的设计思路、设计初衷,很快就可以把脑容量释放出来,重点思考其他问题,代码读起来就会变得轻松了。
最后,当你成为leader、或者团队中的资深工程师、技术专家之后,你势必要负责一部分团队的招聘工作。这个时候,如果你要考察候选人的设计能力、代码能力,那设计模式相关的问题便是一个很好的考察点。
不过,我也了解到,很多面试官实际上对设计模式也并不是很了解,只能拿一些简单的单例模式、工厂模式来考察候选人,而且所出的题目往往都脱离实践,比如,如何设计一个餐厅系统、停车场系统、售票系统等。这些题目都是网上万年不变的老题目,几乎考察不出候选人的能力。在我的专栏中,有200多个真实项目开发中的设计模式相关问题,你跟着看下来,足以让你成为设计模式方面的大牛,再来面试候选人的时候,就不用因为题目老套、脱离实践而尴尬了!
总结一下的话,主要有这样五点:应对面试中的设计模式相关问题;告别写被人吐槽的烂代码;提高复杂代码的设计和开发能力;让读源码、学框架事半功倍;为你的职场发展做铺垫。

今天课堂讨论的话题有两个:

1、聊一聊你对设计模式相关知识的重要性的看法;
2、在你过往的项目开发中,有没有用过某种设计模式?是在什么场景下应用的?解决了什么问题?

(我觉得设计模式是前人提取出来的经验之谈,不了解设计模式的之前,你写的代码可能也有用到设计模式。
了解之后,更能帮助你进一步规范化开发。所有的东西不是必须的,但是站在前人的肩膀上一定是最省力的。)

从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?

对于程序员来说,辨别代码写得“好”还是“烂”,是一个非常重要的能力。这也是我们写出好代码的前提。毕竟,如果我们连什么是好代码、什么是烂代码,都分辨不清,又谈何写出好代码呢?
好代码:
灵活性(flexibility)、可扩展性(extensibility)、可维护性(maintainability)、可读性(readability)、可理解性(understandability)、易修改性(changeability)、可复用(reusability)、可测试性(testability)、模块化(modularity)、高内聚低耦合(high cohesion loose coupling)、高效(high effciency)、高性能(high performance)、安全性(security)、兼容性(compatibility)、易用性(usability)、整洁(clean)、清晰(clarity)、简单(simple)、直接(straightforward)、少即是多(less code is more)、文档详尽(well-documented)、分层清晰(well-layered)、正确性(correctness、bug free)、健壮性(robustness)、鲁棒性(robustness)、可用性(reliability)、可伸缩性(scalability)、稳定性(stability)、优雅(elegant)、好(good)、坏(bad)……
仔细看前面罗列的所有代码质量评价标准,你会发现,有些词语过于笼统、抽象,比较偏向对于整体的描述,比如优雅、好、坏、整洁、清晰等;有些过于细节、偏重方法论,比如模块化、高内聚低耦合、文档详尽、分层清晰等;有些可能并不仅仅局限于编码,跟架构设计等也有关系,比如可伸缩性、可用性、稳定性等。
为了做到有的放矢、有重点地学习,我挑选了其中几个最常用的、最重要的评价标准,来详细讲解,其中就包括:可维护性、可读性、可扩展性、灵活性、简洁性(简单、复杂)、可复用性、可测试性。接下来,我们逐一讲解一下。

1.可维护性(maintainability)

我们首先来看,什么是代码的“可维护性”?所谓的“维护代码”到底包含哪些具体工作?

落实到编码开发,所谓的“维护”无外乎就是修改bug、修改老的代码、添加新的代码之类的工作。所谓“代码易维护”就是指,在不破坏原有代码设计、不引入新的bug的情况下,能够快速地修改或者添加代码。所谓“代码不易维护”就是指,修改或者添加代码需要冒着极大的引入新bug的风险,并且需要花费很长的时间才能完成。

我们知道,对于一个项目来说,维护代码的时间远远大于编写代码的时间。工程师大部分的时间可能都是花在修修bug、改改老的功能逻辑、添加一些新的功能逻辑之类的工作上。所以,代码的可维护性就显得格外重要。

维护、易维护、不易维护这三个概念不难理解。不过,对于实际的软件开发来说,更重要的是搞清楚,如何来判断代码可维护性的好坏。

实际上,可维护性也是一个很难量化、偏向对代码整体的评价标准,它有点类似之前提到的“好”“坏”“优雅”之类的笼统评价。代码的可维护性是由很多因素协同作用的结果。代码的可读性好、简洁、可扩展性好,就会使得代码易维护;相反,就会使得代码不易维护。更细化地讲,如果代码分层清晰、模块化好、高内聚低耦合、遵从基于接口而非实现编程的设计原则等等,那就可能意味着代码易维护。除此之外,代码的易维护性还跟项目代码量的多少、业务的复杂程度、利用到的技术的复杂程度、文档是否全面、团队成员的开发水平等诸多因素有关。

所以,从正面去分析一个代码是否易维护稍微有点难度。不过,我们可以从侧面上给出一个比较主观但又比较准确的感受。如果bug容易修复,修改、添加功能能够轻松完成,那我们就可以主观地认为代码对我们来说易维护。相反,如果修改一个bug,修改、添加一个功能,需要花费很长的时间,那我们就可以主观地认为代码对我们来说不易维护。

你可能会说,这样的评价方式也太主观了吧?没错,是否易维护本来就是针对维护的人来说的。不同水平的人对于同一份代码的维护能力并不是相同的。对于同样一个系统,熟悉它的资深工程师会觉得代码的可维护性还不错,而一些新人因为不熟悉代码,修改bug、修改添加代码要花费很长的时间,就有可能会觉得代码的可维护性不那么好。这实际上也印证了我们之前的观点:代码质量的评价有很强的主观性。

2.可读性(readability)

软件设计大师Martin Fowler曾经说过:“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”翻译成中文就是:“任何傻瓜都会编写计算机能理解的代码。好的程序员能够编写人能够理解的代码。”Google内部甚至专门有个认证就叫作Readability。只有拿到这个认证的工程师,才有资格在code review的时候,批准别人提交代码。可见代码的可读性有多重要,毕竟,代码被阅读的次数远远超过被编写和执行的次数。

我个人认为,代码的可读性应该是评价代码质量最重要的指标之一。我们在编写代码的时候,时刻要考虑到代码是否易读、易理解。除此之外,代码的可读性在非常大程度上会影响代码的可维护性。毕竟,不管是修改bug,还是修改添加功能代码,我们首先要做的事情就是读懂代码。代码读不大懂,就很有可能因为考虑不周全,而引入新的bug。

既然可读性如此重要,那我们又该如何评价一段代码的可读性呢?

我们需要看代码是否符合编码规范、命名是否达意、注释是否详尽、函数是否长短合适、模块划分是否清晰、是否符合高内聚低耦合等等。你应该也能感觉到,从正面上,我们很难给出一个覆盖所有评价指标的列表。这也是我们无法量化可读性的原因。

实际上,code review是一个很好的测验代码可读性的手段。如果你的同事可以轻松地读懂你写的代码,那说明你的代码可读性很好;如果同事在读你的代码时,有很多疑问,那就说明你的代码可读性有待提高了。

3.可扩展性(extensibility)

可扩展性也是一个评价代码质量非常重要的标准。它表示我们的代码应对未来需求变化的能力。跟可读性一样,代码是否易扩展也很大程度上决定代码是否易维护。那到底什么是代码的可扩展性呢?

代码的可扩展性表示,我们在不修改或少量修改原有代码的情况下,通过扩展的方式添加新的功能代码。说直白点就是,代码预留了一些功能扩展点,你可以把新功能代码,直接插到扩展点上,而不需要因为要添加一个功能而大动干戈,改动大量的原始代码。

关于代码的扩展性,在后面讲到“对修改关闭,对扩展开放”这条设计原则的时候,我会来详细讲解,今天我们只需要知道,代码的可扩展性是评价代码质量非常重要的标准就可以了。

4.灵活性(flexibility)

灵活性也是描述代码质量的一个常用词汇。比如我们经常会听到这样的描述:“代码写得很灵活”。那这里的“灵活”该如何理解呢?

尽管有很多人用这个词汇来描述代码的质量。但实际上,灵活性是一个挺抽象的评价标准,要给灵活性下个定义也是挺难的。不过,我们可以想一下,什么情况下我们才会说代码写得好灵活呢?我这里罗列了几个场景,希望能引发你自己对什么是灵活性的思考。

**当我们添加一个新的功能代码的时候,原有的代码已经预留好了扩展点,我们不需要修改原有的代码,只要在扩展点上添加新的代码即可。**这个时候,我们除了可以说代码易扩展,还可以说代码写得好灵活。
**当我们要实现一个功能的时候,发现原有代码中,已经抽象出了很多底层可以复用的模块、类等代码,我们可以拿来直接使用。**这个时候,我们除了可以说代码易复用之外,还可以说代码写得好灵活。
当我们使用某组接口的时候,如果这组接口可以应对各种使用场景,满足各种不同的需求,我们除了可以说接口易用之外,还可以说这个接口设计得好灵活或者代码写得好灵活。
从刚刚举的场景来看,如果一段代码易扩展、易复用或者易用,我们都可以称这段代码写得比较灵活。所以,灵活这个词的含义非常宽泛,很多场景下都可以使用。

5.简洁性(simplicity)

有一条非常著名的设计原则,你一定听过,那就是KISS原则:“Keep It Simple,Stupid”。这个原则说的意思就是,尽量保持代码简单。代码简单、逻辑清晰,也就意味着易读、易维护。我们在编写代码的时候,往往也会把简单、清晰放到首位。

不过,很多编程经验不足的程序员会觉得,简单的代码没有技术含量,喜欢在项目中引入一些复杂的设计模式,觉得这样才能体现自己的技术水平。实际上,思从深而行从简,真正的高手能云淡风轻地用最简单的方法解决最复杂的问题。这也是一个编程老手跟编程新手的本质区别之一。

除此之外,虽然我们都能认识到,代码要尽量写得简洁,符合KISS原则,但怎么样的代码才算足够简洁?不是每个人都能很准确地判断出来这一点。所以,在后面的章节中,当我们讲到KISS原则的时候,我会通过具体的代码实例,详细给你解释,“为什么KISS原则看似非常简单、好理解,但实际上用好并不容易”。今天,我们就暂且不展开详细讲解了。

6.可复用性(reusability)

代码的可复用性可以简单地理解为,尽量减少重复代码的编写,复用已有的代码。在后面的很多章节中,我们都会经常提到“可复用性”这一代码评价标准。

比如,当讲到面向对象特性的时候,我们会讲到继承、多态存在的目的之一,就是为了提高代码的可复用性;当讲到设计原则的时候,我们会讲到单一职责原则也跟代码的可复用性相关;当讲到重构技巧的时候,我们会讲到解耦、高内聚、模块化等都能提高代码的可复用性。可见,可复用性也是一个非常重要的代码评价标准,是很多设计原则、思想、模式等所要达到的最终效果。

实际上,代码可复用性跟DRY(Don’t Repeat Yourself)这条设计原则的关系挺紧密的,所以,在后面的章节中,当我们讲到DRY设计原则的时候,我还会讲更多代码复用相关的知识,比如,“有哪些编程方法可以提高代码的复用性”等。

7.可测试性(testability)

相对于前面六个评价标准,代码的可测试性是一个相对较少被提及,但又非常重要的代码质量评价标准。代码可测试性的好坏,能从侧面上非常准确地反应代码质量的好坏。代码的可测试性差,比较难写单元测试,那基本上就能说明代码设计得有问题。关于代码的可测试性,我们在重构那一部分,会花两节课的时间来详细讲解。现在,你暂时只需要知道,代码的可测试性非常重要就可以了。

如何写出易维护、易读、易扩展、灵活、简洁、可复用、可测试的代码。

答案:我们需要掌握一些更加细化、更加能落地的编程方法论,包括面向对象设计思想、设计原则、设计模式、编码规范、重构技巧等。而所有这些编程方法论的最终目的都是为了编写出高质量的代码。
比如,面向对象中的继承、多态能让我们写出可复用的代码;编码规范能让我们写出可读性好的代码;设计原则中的单一职责、DRY、基于接口而非实现、里式替换原则等,可以让我们写出可复用、灵活、可读性好、易扩展、易维护的代码;设计模式可以让我们写出易扩展的代码;持续重构可以时刻保持代码的可维护性等等。具体这些编程方法论是如何提高代码的可维护性、可读性、可扩展性等等的呢?我们在后面的课程中慢慢来学习。

思考:

1.如何评价代码质量的高低?

代码质量的评价有很强的主观性,描述代码质量的词汇也有很多,比如可读性、可维护性、灵活、优雅、简洁等,这些词汇是从不同的维度去评价代码质量的。它们之间有互相作用,并不是独立的,比如,代码的可读性好、可扩展性好就意味着代码的可维护性好。代码质量高低是一个综合各种因素得到的结论。我们并不能通过单一的维度去评价一段代码的好坏。

2.最常用的评价标准有哪几个?

最常用到几个评判代码质量的标准是:可维护性、可读性、可扩展性、灵活性、简洁性、可复用性、可测试性。其中,可维护性、可读性、可扩展性又是提到最多的、最重要的三个评价标准。

3.如何才能写出高质量的代码?

要写出高质量代码,我们就需要掌握一些更加细化、更加能落地的编程方法论,这就包含面向对象设计思想、设计原则、设计模式、编码规范、重构技巧等等,这也是我们后面课程学习的重点。

面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?

面向对象

现在,主流的编程范式或者是编程风格有三种,它们分别是面向过程、面向对象和函数式编程。面向对象这种编程风格又是这其中最主流的。现在比较流行的编程语言大部分都是面向对象编程语言。大部分项目也都是基于面向对象编程风格开发的。面向对象编程因为其具有丰富的特性(封装、抽象、继承、多态),可以实现很多复杂的设计思路,是很多设计原则、设计模式编码实现的基础。

所以,在专栏的最开始,我们会详细地讲解面向对象编程的相关的知识,为学习后面的内容做铺垫。对于这部分内容,你需要掌握下面这7个大的知识点。

1、面向对象的四大特性:封装、抽象、继承、多态
2、面向对象编程与面向过程编程的区别和联系
3、面向对象分析、面向对象设计、面向对象编程
4、接口和抽象类的区别以及各自的应用场景
5、基于接口而非实现编程的设计思想
6、多用组合少用继承的设计思想
7、面向过程的贫血模型和面向对象的充血模型

设计原则

设计原则是指导我们代码设计的一些经验总结。设计原则这块儿的知识有一个非常大的特点,那就是这些原则听起来都比较抽象,定义描述都比较模糊,不同的人会有不同的解读。所以,如果单纯地去记忆定义,对于编程、设计能力的提高,意义并不大。对于每一种设计原则,我们需要掌握它的设计初衷,能解决哪些编程问题,有哪些应用场景。只有这样,我们才能在项目中灵活恰当地应用这些原则。

对于这一部分内容,你需要透彻理解并且掌握,如何应用下面这样几个常用的设计原则。

1、SOLID原则-SRP单一职责原则
2、SOLID原则-OCP开闭原则
3、SOLID原则-LSP里式替换原则
4、SOLID原则-ISP接口隔离原则
5、SOLID原则-DIP依赖倒置原则
6、DRY原则、KISS原则、YAGNI原则、LOD法则

设计模式

设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。大部分设计模式要解决的都是代码的可扩展性问题。设计模式相对于设计原则来说,没有那么抽象,而且大部分都不难理解,代码实现也并不复杂。这一块的学习难点是了解它们都能解决哪些问题,掌握典型的应用场景,并且懂得不过度应用。

经典的设计模式有23种。随着编程语言的演进,一些设计模式(比如Singleton)也随之过时,甚至成了反模式,一些则被内置在编程语言中(比如Iterator),另外还有一些新的模式诞生(比如Monostate)。

在专栏中,我们会重点讲解23种经典的设计模式。它们又可以分为三大类:创建型、结构型、行为型。对于这23种设计模式的学习,我们要有侧重点,因为有些模式是比较常用的,有些模式是很少被用到的。对于常用的设计模式,我们要花多点时间理解掌握。对于不常用的设计模式,我们只需要稍微了解即可。

我按照类型和是否常用,对专栏中讲到的这些设计模式,进行了简单的分类,具体如下所示。
1.创建型
常用的有:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式。

不常用的有:原型模式。

2.结构型
常用的有:代理模式、桥接模式、装饰者模式、适配器模式。

不常用的有:门面模式、组合模式、享元模式。

3.行为型
常用的有:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式。

不常用的有:访问者模式、备忘录模式、命令模式、解释器模式、中介模式。

编程规范

编程规范主要解决的是代码的可读性问题。编码规范相对于设计原则、设计模式,更加具体、更加偏重代码细节。即便你可能对设计原则不熟悉、对设计模式不了解,但你最起码要掌握基本的编码规范,比如,如何给变量、类、函数命名,如何写代码注释,函数不宜过长、参数不能过多等等。

对于编码规范,考虑到很多书籍已经讲得很好了(比如《重构》《代码大全》《代码整洁之道》等)。而且,每条编码规范都非常简单、非常明确,比较偏向于记忆,你只要照着来做可以。它不像设计原则,需要融入很多个人的理解和思考。所以,在这个专栏中,我并没有花太多的篇幅来讲解所有的编码规范,而是总结了我认为的最能改善代码质量的20条规范。如果你暂时没有时间去看那些经典的书籍,看我这些就够了。

除此之外,专栏并没有将编码规范单独作为一个模块来讲解,而是跟重构放到了一起。之所以这样做,那是因为我把重构分为大重构和小重构两种类型,而小重构利用的知识基本上就是编码规范。

除了编码规范,我们还会介绍一些代码的坏味道,让你知道什么样的代码是不符合规范的,应该如何优化。参照编码规范,你可以写出可读性好的代码;参照代码的坏味道,你可以找出代码存在的可读性问题。

代码重构

在软件开发中,只要软件在不停地迭代,就没有一劳永逸的设计。随着需求的变化,代码的不停堆砌,原有的设计必定会存在这样那样的问题。针对这些问题,我们就需要进行代码重构。重构是软件开发中非常重要的一个环节。持续重构是保持代码质量不下降的有效手段,能有效避免代码腐化到无可救药的地步。

而重构的工具就是我们前面罗列的那些面向对象设计思想、设计原则、设计模式、编码规范。实际上,设计思想、设计原则、设计模式一个最重要的应用场景就是在重构的时候。我们前面讲过,虽然使用设计模式可以提高代码的可扩展性,但过度不恰当地使用,也会增加代码的复杂度,影响代码的可读性。在开发初期,除非特别必须,我们一定不要过度设计,应用复杂的设计模式。而是当代码出现问题的时候,我们再针对问题,应用原则和模式进行重构。这样就能有效避免前期的过度设计。

对于重构这部分内容,你需要掌握以下几个知识点:

1、重构的目的(why)、对象(what)、时机(when)、方法(how);
2、保证重构不出错的技术手段:单元测试和代码的可测试性;
3、两种不同规模的重构:大重构(大规模高层次)和小重构(小规模低层次)。
希望你学完这部分内容之后,不仅仅是掌握一些重构技巧、套路,更重要的是建立持续重构意识,把重构当作开发的一部分,融入到日常的开发中。

五者之间的联系

关于面向对象、设计原则、设计模式、编程规范和代码重构,这五者的关系我们前面稍微提到了一些,我这里再总结梳理一下。

1、面向对象编程因为其具有丰富的特性(封装、抽象、继承、多态),可以实现很多复杂的设计思路,是很多设计原则、设计模式等编码实现的基础。
2、设计原则是指导我们代码设计的一些经验总结,对于某些场景下,是否应该应用某种设计模式,具有指导意义。比如,“开闭原则”是很多设计模式(策略、模板等)的指导原则。
3、设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。应用设计模式的主要目的是提高代码的可扩展性。从抽象程度上来讲,设计原则比设计模式更抽象。设计模式更加具体、更加可执行。
4、编程规范主要解决的是代码的可读性问题。编码规范相对于设计原则、设计模式,更加具体、更加偏重代码细节、更加能落地。持续的小重构依赖的理论基础主要就是编程规范。
5、重构作为保持代码质量不下降的有效手段,利用的就是面向对象、设计原则、设计模式、编码规范这些理论。

实际上,面向对象、设计原则、设计模式、编程规范、代码重构,这五者都是保持或者提高代码质量的方法论,本质上都是服务于编写高质量代码这一件事的。当我们追本逐源,看清这个本质之后,很多事情怎么做就清楚了,很多选择怎么选也清楚了。比如,在某个场景下,该不该用这个设计模式,那就看能不能提高代码的可扩展性;要不要重构,那就看重代码是否存在可读、可维护问题等。
在这里插入图片描述

思考:

1、在今天讲到的内容中,你觉得哪一部分内容对提高代码质量最有效?为什么?除了我罗列的这些内容之外,你还知道哪些可以提高代码质量的方法?
2、我们知道,最经典的设计模式书籍是GoF的《设计模式》,它的中文全称是《设计模式:可复用面向对象软件的基础》,英文全称是“Design Patterns: Elements of Reusable Object-Oriented Software”。为什么它在标题中会特意提到“面向对象”呢?
(这些内容对提高代码质量都挺有效的。
面向对象:是代码复用的基石。
设计原则:遵循好的设计原则可以有效抵抗代码坏味道的产生,提升代码的可维护性。
编程规范:遵循统一的良好的编程规范可以有效降低代码的理解成本,提升代码的可读性。
设计模式:适当运用设计模式可以提升代码的可扩展性和灵活性。
重构:即时重构、小步快跑可以有效改善代码质量,重构所需的基础应该就是面向对象、设计原则、编程规范、设计模式了。
我个人认为通过DDD产出好的领域模型,以及TDD这种测试先行的设计思想,也是很好的提升代码质量的方法。
为什么在标题里特意提到面向对象?
我的理解是面向对象是实现设计模式的基础,设计模式则是面向对象编程范畴里一些列优秀实践的总结)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值