设计模式导读

283 阅读16分钟

一、 为什么要学习设计模式?

计算机组成原理、操作系统、编译原理等基础知识很重要,只有夯实基础,才能走的更远;实际上,这些基础知识很难直接转化成开发“生产力”;但是呢,它能够潜移默化地、间接地提高你对技术的理解

设计模式和数据结构、算法是一道的,相比上述基础的学科,设计模式能更直接地提高你的开发能力

数据结构与算法是教你如何写出高效代码,设计模式讲的是如何写出可拓展、可读、可维护的高质量代码,所以,它们跟平时的编码会有直接的关系,也会影响到你的开发能力。

1. 应对面试

最功利性,也是最直接的目的:应对面试。 硅谷外企和国内大公司的面试,比较注重候选人的基本功,经常会拿算法、设计模式之类的问题来考察候选人。

平时多注重设计模式相关知识的积累,只需要在面试前花很短的时间重新温习一下,就可以自信满满地去面试了,甚至能成为面试中的亮点。

2. 告别写被别人吐槽的烂代码

代码能力是一个程序员最基础的能力,是基本功,是展示一个程序员基础素养的最直接的衡量标准。你写的代码,实际上就是你的名片。

烂代码有什么特点呢?

  • 命名不规范
  • 类设计不合理
  • 分层不清晰
  • 没有模块化概念
  • 代码结构混乱
  • 高度耦合
  • 没有抽象
  • 逻辑结构太复杂
  • 代码参数太多
  • 参数没有做任何封装 这样的代码维护起来非常费劲,添加或者修改一个功能,常常会牵一发而动全身,让你无从下手,恨不得将全部的代码删掉重写!

另外,在工作中,看到好代码时,都会立刻对作者产生无比的好感。从代码就能看出,这是一个基础扎实的高潜员工,值得培养,有前途。因此,代码写的好,能让你在团队中脱引而出。

写出令人称道的好代码,成为团队中的代码标杆!追求极致,写出一份漂亮的代码,自己也会很有成就感。

"Talk is cheap, show me your code."

3. 提高复杂代码的设计和开发能力

大部分工程师比较熟悉的都是编程语言、工具和框架这些东西,因为每天的工作就是在框架里根据业务需求,填充代码。相对来说,这样的工作并不需要具备很强的代码设计能力,只要单纯地能够理解业务,翻译成代码就可以了。

但如果要让你开发一个跟业务无关的比较通用的功能模块,面对这样稍微复杂代码设计和开发,就会显得有点力不从心,不知从何下手了。只是完成功能、代码能用,可能并不复杂,本着自我成长、追求极致的想法,写出易扩展、易用、易维护的代码,并不容易

写代码大部分时间都是体力活,类似于水泥工抹灰砌墙,很多一旦形成肌肉记忆变成为了熟练工种。反倒是一些设计问题,一开始如果不考虑清楚,宁可不做。

要让写出高质量代码成为我们的习惯。

4. 让读源码、学框架事半功倍

一个有追求的程序员,对技术的积累,既要有广度,也要有深度。因此在学习框架、中间件的时候,去研究研究原理,读一读源码,而不只是略知皮毛,会用而已。

但在看源码的时候,经常会遇到看不懂、看不下去的问题。究其原因,在于积累的基本功还不够,以目前的能力还不足以看懂这些代码。

优秀的开源项目、框架、中间件,代码量、类的个数都会比较多,类结构、类之间的关系及其复杂,常常调用来调用去。为了保证代码的扩展性、灵活性、可维护性等,代码中会使用到很多设计模式、设计原则或者设计思想。所以看代码的时候,琢磨不透作者的设计思路,一些很明显的设计思路,要花费很多时间才能参悟。反之,如果对设计模式、原则、思想非常了解。一眼就能参透作者的设计思路、设计初衷,就可以把重点放在其它问题上了,阅读代码便会简单起来了。

可能还存在一个隐藏的问题,连自己都没有发现,那就是你觉得自己看懂了,实际上,里面的精髓并没有get到多少。因为没有积累深厚的基本功,只能了解哥皮毛,看个热闹。另外一层的原因,可能也是因为读源码没有很好的检测方式,最好的检测方式就是输出。

5. 职业发展做铺垫

低级别的开发工程师,只需要把框架、开发工具、编程语言用熟练,再做几个项目练练手,基本上技能应付平时的开发工作了。可我们希望在职场有更高的成就、更好的发展,那就要重视基本功的训练、基础知识的积累。

在技术这条职场道路上,成长到一定阶段后,势必要承担一些指导培养新人,以及code review的工作了。这个时候,如果对“什么是好代码?如何写好代码?”不了解,那又该如何指导别人,如何让人家信服呢?

还有,对于一个技术leader,负责一个项目整体的开发工作,需要为开发进度、开发效率和项目质量负责。没人希望团队堆砌垃圾代码,让整个项目无法维护,拉低整个团队的开发效率。

最后,当你成为面试官去招聘的时候,便有能力考察候选人的设计能力、代码能力。

种一棵树,最好的时间是三年前,其次就是现在。投资要趁早,才能尽早享受福利;工作、学习和读书,也是一样的道理。

设计模式作为与编码、开发有着直接关系的基础知识,是现在就要开始学习的。早点学习,以后的项目都可以拿来锻炼,每写一行代码都是对内功的利用和加深,是可以受益一整个职业生涯的事情。

二、 如何评价代码质量的好坏?

对于程序员来说,辨别代码写的“好”还是“烂”,是一个非常重要的能力。这也是我们写出好代码的前提,毕竟,如果连什么是“好”代码,什么是“烂”代码都分辨不清,又何谈写出好代码来呢?

普遍认知,如果连一个事物的好坏我们都区分不出来,那又何谈做好一件事情呢?

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

无论是“好”还是“烂”去描述代码质量,都是一个很笼统的说法,比较专业、丰富和细化地描述代码质量词汇如下:

灵活性(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)……

即使通过其中几个词,也很难全面地衡量代码的质量,因为这些词是从不同维度来说的,代码质量也是一个综合各种因素得到的结论。此处之外,不同纬度的评价也不是完全独立的,有些是具有包含关系、重叠关系或者可以相互影响的。

各种评价维度不是非黑即白,比如,不能将代码分为可读与不可读,如果用数字来量化代码可读性的话,那它是应该是一个连续的区间值,而不是0、1这种离散值。

对一段代码的质量评价,往往具有很强的主观性,每个人的评判标准都不太一样,跟工程师的自身经验也有很大关系。

2. 常用的评价标准有哪些?

2.1 可维护性(maintainability)

所谓“代码易维护”就是指,在不破坏原有代码设计、不引入新bug的情况下,能够快速地修改或者添加代码。

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

实际上,可维护性也是一个很难量化、偏向对代码整体的评价标准。代码的可维护性是由很多因素协同作用的结果,代码的可读性好、简洁、可拓展性好,就会使得代码易维护。更细化地讲,如果代码分层清晰、模块化好、高内聚低耦合、遵从给予接口而非实现编程的刷机原则等等,那就可能意味着代码易维护。此外,代码的易维护性跟项目代码量的多少、业务的复杂程度、利用到的技术的复杂程度、文档是否全面、团队成员的开发水平等诸多因素有关。

从正面去分析一个代码是否易维护有点难度,那么,从侧面上来看,如果bug容易修复,修改、添加功能能够轻松完成,那我们就主观地认为代码对我们来说易维护。

2.2 可读性(readability)

如何评价代码的可读性?

  • 是否符合编码规范
  • 命名是否达意
  • 注释是否详尽
  • 函数是否长短合适
  • 模块划分是否清晰
  • 是否符合高内聚低耦合
  • …… Code review是一个很好的测试代码可读性的手段。如果你的同事可以轻松读懂你写的代码,那说明你的代码可读性很好;如果同事读你的代码,有很多疑问,那就说明你的代码可读性有待提高了。

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

2.3 可扩展性(extensibility)

可拓展性表示代码应对未来需求变化的能力。在不修改或少量修改原有代码的情况下,通过扩展的方式添加新的功能代码。

说通透一点就是,代码预留一些功能拓展点,可以把新功能的代码,直接插到扩展点上,而不需要因为要添加一个功能而大动干戈,改动大量的原始代码。

设计原则:对修改关闭,对拓展开放。

2.4 灵活性(flexibility)

如果一段代码易扩展、易复用或者易用,都可以称为这段代码写的比较灵活。

2.5 简洁性(simplicity)

KISS原则:Keep it simple, stupid.

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

2.6 可复用性(reusability)

简单理解就是,尽量减少重复代码的编写,复用已有的代码。是很多设计原则、思想、模式等所要达到的最终效果。 DRP原则——Don't repeat yourself。

2.7 可测试性(testability) 代码可测试性的好坏,能从侧面上非常准确地反应代码质量的好坏。代码的课测试性差,比较难写单元测试,那基本上就能说明代码设计得有问题。

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

需要掌握一些更加细化、更加能落地的编程方法论,包括面向对象设计思想、设计原则、设计模式、编码规范、重构技巧等。

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

3.1 面向对象

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

面向对象编程需要掌握的7个大的知识点

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

面向对象编程支持最好的还是JAVA吧,Golang面向对象非常简洁,去掉了继承、方法重载、构造函数和析构函数等等。

3.2 设计原则

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

需要透彻理解并且掌握的几个常用设计原则

  • SOLID原则 - SRP单一职责原则
  • SOLID原则 - OCP开闭原则
  • SOLID原则 - LSP里式替换原则
  • SOLID原则 - ISP接口隔离原则
  • SOLID原则 - DIP依赖倒置原则
  • DRY(Don't Repeat Yourself)原则、KISS(Keep It Simple, Stupid)原则、YAGNI(You Ain't Gonna Need It)原则、LOD(Law of Demeter)法则

3.3 设计模式

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

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

类型常用不常用
创建型单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式原型模式
结构型代理模式、桥接模式、装饰者模式、适配器模式门面模式、组合模式、享元模式
行为型观察者模式、模板模式、策略模式、责任链模式、迭代器模式、状态模式访问者模式、备忘录模式、命令模式、解释器模式、中介模式

3.4 编程规范

编程规范主要解决的是代码可读性问题。编程规范相对于设计原则、设计模式,更加具体、更加偏重代码细节。常见的一些编程规范比如:如何给变量、类、函数命名,如何写代码注释、函数不宜过长、参数不能过多等等。

对于编码规范,《重构》、《代码大全》、《代码整洁之道》已经讲的很好了。而且,每条编码规范都非常简单、非常明确,比较偏向于记忆,只要照着来做就可以。不像设计原则,需要融入很多个人的理解和思考。

可以使用通用的lint工具,来约束项目整体编码风格和规范,对于Golang,可以安装golangci-lint插件对代码进行检查。

3.5 代码重构

重构是软件开发中非常重要的一个环节,持续重构是保持代码质量不下降的有效手段,能有效避免代码腐化到无可救药的地步。

使用设计模式可以提高代码的可扩展性,但过度不恰当地使用,也会增加代码的复杂度,影响代码的可读性。在开发初期,除非特别必须,我们一定不要过度设计,应用复杂的设计模式。而是当代码出现问题的时候,我们再针对问题,应用原则和模式进行重构。

对于重构,需要掌握几个知识点:

  • 重构的目的(why)、对象(what)、时机(when)、方法(how);
  • 保证重构不出错的技术手段:单元测试和代码的可测试性;
  • 两种不同规模的重构:大重构(大规模高层次)和小重构(小规模低层次)。

3.6 总结

关于面向对象、设计原则、设计模式、编程规范和代码重构,它们之间的关系是:

  1. 面向对象,可以实现很多复杂的设计思路,是很多设计原则、设计模式等编码实现的基础。
  2. 设计原则是指导代码设计的一些经验总结。
  3. 设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。应用设计模式的主要目的是提高代码的可拓展性。从抽象程度上来讲,设计原则比设计模式更抽象;设计模式更加具体、更加可执行。
  4. 编程规范主要解决的是代码的可读性问题。更加具体、更加偏重代码细节、更加能落地。持续的小重构依赖的理论基础就是编程规范。
  5. 重构作为保持代码质量不下降的有效手段,利用的就是面向对象、设计原则、设计模式、编码规范这些理论。