设计模式之美
Last updated
Was this helpful?
Last updated
Was this helpful?
算法让代码更高效,设计模式让代码高质量(可读性、扩展性、)。
geek专栏:设计模式之美 https://time.geekbang.org/column/article/160463
王争: 研究生毕业后我就加入了 Google,至今我还清晰地记得,我第一次提交代码的时候,短短的 100 多行代码,被同事 review 出了 n 多问题,来来回回改了不下十几个版本才提交上去。我当时有很大的逆反心理,觉得有必要浪费这么多时间在如此细节的编码上吗?只要代码能用、能解决问题不就够了吗? 写代码可以说是程序员天天要干的事情,要是代码都写不好,最基本的看家本领都练不好,成天堆砌烂代码,写代码还有啥意思呢?那还干啥程序员啊!写出“能用”代码的人比比皆是,但是,并不是每个人都能写出“好用”的代码。只会写能用的代码,我们永远成长不成大牛,成长不成最优秀的那批人。 我相信,很多工程师都很重视代码质量,毕竟谁也不想写被人吐槽的烂代码。但是,就我的了解来看,毫不夸张地讲,很多工程师,甚至一些 BAT 的员工,代码都写得惨不忍睹。一方面,在目前这种快糙猛的开发环境下,很多工程师并没有太多时间去思考如何写高质量代码;另一方面,在烂代码的熏陶下,在没有人指导的环境里,很多工程师也搞不大清楚高质量代码到底长什么样。 这些年的工作经历中,我见过太多的烂代码,比如命名不规范、类设计不合理、分层不清晰、没有模块化概念、代码结构混乱、高度耦合等等。这样的代码维护起来非常费劲,添加或者修改一个功能,常常会牵一发而动全身,让你无从下手,恨不得将全部的代码删掉重写! 如何分层、分模块?应该怎么划分类?每个类应该具有哪些属性、方法?怎么设计类之间的交互?该用继承还是组合?该使用接口还是抽象类?怎样做到解耦、高内聚低耦合?该用单例模式还是静态方法?用工厂模式创建对象还是直接 new 出来?如何避免引入设计模式提高扩展性的同时带来的降低可读性问题? 优秀的开源项目、框架、中间件,代码量、类的个数都会比较多,类结构、类之间的关系极其复杂,常常调用来调用去。所以,为了保证代码的扩展性、灵活性、可维护性等,代码中会使用到很多设计模式、设计原则或者设计思想。
可维护性、可读性、可扩展性、灵活性、简洁性(简单、复杂)、可复用性、可测试性
可维护性
可维护性很难量化,因人而异。但也有共性,玄。
可读性
软件设计大师 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,还是修改添加功能代码,我们首先要做的事情就是读懂代码。
可扩展性
代码的可扩展性表示,我们在不修改或少量修改原有代码的情况下,通过扩展的方式添加新的功能代码。说直白点就是,代码预留了一些功能扩展点,你可以把新功能代码,直接插到扩展点上,而不需要因为要添加一个功能而大动干戈,改动大量的原始代码。
灵活性
当我们添加一个新的功能代码的时候,原有的代码已经预留好了扩展点,我们不需要修改原有的代码,只要在扩展点上添加新的代码即可。这个时候,我们除了可以说代码易扩展,还可以说代码写得好灵活。
当我们要实现一个功能的时候,发现原有代码中,已经抽象出了很多底层可以复用的模块、类等代码,我们可以拿来直接使用。这个时候,我们除了可以说代码易复用之外,还可以说代码写得好灵活。
当我们使用某组接口的时候,如果这组接口可以应对各种使用场景,满足各种不同的需求,我们除了可以说接口易用之外,还可以说这个接口设计得好灵活或者代码写得好灵活。
简洁性(简单、复杂)
思从深而行从简,真正的高手能云淡风轻地用最简单的方法解决最复杂的问题。这也是一个编程老手跟编程新手的本质区别之一。
可复用性
当讲到面向对象特性的时候,我们会讲到继承、多态存在的目的之一,就是为了提高代码的可复用性;当讲到设计原则的时候,我们会讲到单一职责原则也跟代码的可复用性相关;当讲到重构技巧的时候,我们会讲到解耦、高内聚、模块化等都能提高代码的可复用性。
实际上,代码可复用性跟 **DRY(Don’t Repeat Yourself)**这条设计原则的关系挺紧密的,所以,在后面的章节中,当我们讲到 DRY 设计原则的时候,我还会讲更多代码复用相关的知识,比如,“有哪些编程方法可以提高代码的复用性”等。
可测试性
代码可测试性的好坏,能从侧面上非常准确地反应代码质量的好坏。代码的可测试性差,比较难写单元测试,那基本上就能说明代码设计得有问题。
封装
封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或者数据。它需要编程语言提供权限访问控制语法来支持,例如 Java 中的 private、protected、public 关键字。封装特性存在的意义,一方面是保护数据不被随意修改,提高代码的可维护性;另一方面是仅暴露有限的必要接口,提高类的易用性。
抽象
封装主要讲的是如何隐藏信息、保护数据,而抽象讲的是如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。
抽象这个概念是一个非常通用的设计思想,并不单单用在面向对象编程中,也可以用来指导架构设计等。而且这个特性也并不需要编程语言提供特殊的语法机制来支持,只需要提供“函数”这一非常基础的语法机制,就可以实现抽象特性、所以,它没有很强的“特异性”,有时候并不被看作面向对象编程的特性之一。
继承
继承是用来表示类之间的 is-a 关系,分为两种模式:单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类。为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持。继承主要是用来解决代码复用的问题。
多态
多态就是同一个接口,使用不同的实例/对象而执行不同操作。多态性使得程序调用的函数是在运行时动态确定的,而不是在编译时静态确定的。
https://en.wikipedia.org/wiki/Polymorphism_(computer_science)
In programming language theory and type theory, polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types.
多态分为三类:
特设多态 Ad hoc polymorphism: defines a common interface for an arbitrary set of individually specified types.
即函数/方法重载
参数多态 Parametric polymorphism: not specifying concrete types and instead use abstract symbols that can substitute for any type.
比如c++中的模版类/方法。
子类型 Subtyping (also called subtype polymorphism or inclusion polymorphism): when a name denotes instances of many different classes related by some common superclass.
多态是指子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。多态这种特性也需要编程语言提供特殊的语法机制来实现,比如继承、接口类、duck-typing。多态可以提高代码的扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。
C++通过继承和虚函数实现,python通过duck-typing实现。
经典的设计模式有 23 种,可以分为三大类:创建型、结构型、行为型。。随着编程语言的演进,一些设计模式(比如 Singleton)也随之过时,甚至成了反模式,一些则被内置在编程语言中(比如 Iterator),另外还有一些新的模式诞生(比如 Monostate)。
常用的有:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式。
不常用的有:原型模式。
常用的有:代理模式、桥接模式、装饰者模式、适配器模式。
不常用的有:门面模式、组合模式、享元模式。
常用的有:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式。
不常用的有:访问者模式、备忘录模式、命令模式、解释器模式、中介模式。