历经两个月的时间,终于看完了王争的《设计模式之美》,共73篇文章。
这本书还是很值得一读的,作者对设计理念、设计模式思考的比较深,而且将这些思想运用到实际的工作中,使设计模式显得不那么空;另外这本书花了很大的篇幅来讲设计的理念,讲的比较通透,这是很难得,因为无论哪种设计模式,最终都会映射回理念,设计理念才是根本性的东西。
通过阅读设计模式相关的书籍,现在已经完成了5篇文章:
Go设计模式(5)-类图符号表示法
Go设计模式(4)-代码编写优化
Go设计模式(4)-代码编写
Go设计模式(3)-设计原则
Go设计模式(2)-面向对象分析与设计
Go设计模式(1)-语法
后面也会进入设计模式文章的书写,这么做虽然导致学习进度慢了,但是能够将这些知识掌握的更加扎实,那就是值得的。
下面是做的读书笔记,大家有兴趣可以看一下,如果觉得合适,可以去极客时间购买全本。
读书笔记开篇词
只会写能用的代码,我们永远成长不成大牛,成长不成最优秀的那批人
写好的代码是为未来节省时间
为什么说每个程序员都要尽早地学习并掌握设计模式相关知识
应对面试中的设计模式相关问题;
告别写被人吐槽的烂代码;
提高复杂代码的设计和开发能力;
让读源码、学框架事半功倍;
为你的职场发展做铺垫
从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?
可维护性:所谓“代码易维护”就是指,在不破坏原有代码设计、不引入新的bug的情况下,能够快速地修改或者添加代码。
可读性:需要看代码是否符合编码规范、命名是否达意、注释是否详尽、函数是否长短合适、模块划分是否清晰、是否符合高内聚低耦合等等
可扩展性:代码的可扩展性表示,我们在不修改或少量修改原有代码的情况下,通过扩展的方式添加新的功能代码。
灵活性:如果一段代码易扩展、易复用或者易用,我们都可以称这段代码写得比较灵活。所以,灵活这个词的含义非常宽泛,很多场景下都可以使用
简洁性:代码简单、逻辑清晰,也就意味着易读、易维护
可复用性:尽量减少重复代码的编写,复用已有的代码
可测试性:代码的可测试性差,比较难写单元测试,那基本上就能说明代码设计得有问题
面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
理论一:当谈论面向对象的时候,我们到底在谈论什么?
面向对象编程是一种编程范式或编程风格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石。
面向对象编程语言是支持类或对象的语法机制,并有现成的语法机制,能方便地实现面向对象编程四大特性(封装、抽象、继承、多态)的编程语言。
理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?
封装:也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据。
抽象:人类处理复杂性的有效手段
继承:继承最大的一个好处就是代码复用,用来表示类之间的is-a关系
多态:提高代码的扩展性和复用性
理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
面向对象编程比起面向过程编程,更能应对这种复杂类型的程序开发;面向对象编程相比面向过程编程,具有更加丰富的特性(封装、抽象、继承、多态),利用这些特性编写出来的代码,更加易扩展、易复用、易维护;
理论四:哪些代码设计看似是面向对象,实际是面向过程的?
面向过程编程风格符合人的流程化思维方式;而面向对象编程风格正好相反,是一种自底向上的思考方式。
滥用getter、setter方法
Constants类、Utils类的设计问题
基于贫血模型的开发模式
理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
抽象类实际上就是类,只不过是一种特殊的类,这种类不能被实例化为对象,只能被子类继承。我们知道,继承关系是一种is-a的关系,那抽象类既然属于类,也表示一种is-a的关系。相对于抽象类的is-a关系来说,接口表示一种has-a关系,表示具有某些功能。对于接口,有一个更加形象的叫法,那就是协议(contract)。
抽象类更多的是为了代码复用,抽象类能够优雅的保证多态特性
接口就更侧重于解耦
如果我们要表示一种is-a的关系,并且是为了解决代码复用的问题,我们就用抽象类;如果我们要表示一种has-a关系,并且是为了解决抽象而非代码复用的问题,那我们就可以使用接口。
理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
基于接口而非实现编程”这条原则的另一个表述方式,是“基于抽象而非实现编程”。
越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性,越能应对未来的需求变化。好的代码设计,不仅能应对当下的需求,而且在将来需求发生变化的时候,仍然能够在不破坏原有代码设计的情况下灵活应对。
理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
继承有诸多作用,但继承层次过深、过复杂,也会影响到代码的可维护性
继承主要有三个作用:表示is-a关系,支持多态特性,代码复用。而这三个作用都可以通过组合、接口、委托三个技术手段来达成。除此之外,利用组合还能解决层次过深、过复杂的继承关系影响代码可维护性的问题。
实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
只包含数据,不包含业务逻辑的类,就叫作贫血模型
数据和对应的业务逻辑被封装到同一个类中,叫充血模型
基于贫血模型的传统的开发模式,重Service轻BO;基于充血模型的DDD开发模式,轻Service重Domain
实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
对于支付这样的类似转账的操作,我们在操作两个钱包账户余额之前,先记录交易流水,并且标记为“待执行”,当两个钱包的加减金额都完成之后,我们再回过头来,将交易流水标记为“成功”。在给两个钱包加减金额的过程中,如果有任意一个操作失败,我们就将交易记录的状态标记为“失败”。我们通过后台补漏Job,拉取状态为“失败”或者长时间处于“待执行”状态的交易记录,重新执行或者人工介入处理。
实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?
面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程(OOP),是面向对象开发的三个主要环节
实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
划分职责进而识别出有哪些类:罗列名词或者功能点
定义类及其属性和方法:我们识别出需求描述中的动词,作为候选的方法,再进一步过滤筛选出真正的方法,把功能点中涉及的名词,作为候选属性,然后同样再进行过滤筛选。
定义类与类之间的交互关系:泛化、实现、关联、聚合、组合、依赖,只保留了四个关系:泛化、实现、组合、依赖
将类组装起来并提供执行入口
面向对象分析的产出是详细的需求描述
面向对象设计的产出是类
理论一:对于单一职责原则,如何判定某个类的职责是否够单一?
类中的代码行数、函数或者属性过多;
类依赖的其他类过多,或者依赖类的其他类过多;
私有方法过多;
比较难给类起一个合适的名字;
类中大量的方法都是集中操作类中的某几个属性。
单一职责原则:一个类只负责完成一个职责或者功能。不要设计大而全的类,要设计粒度小、功能单一的类。单一职责原则是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性
实际上,在真正的软件开发中,我们也没必要过于未雨绸缪,过度设计。所以,我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构。一些常用判断指标:
理论二:如何做到对扩展开放、修改关闭?扩展和修改各指什么?
开闭原则:添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。关于定义,我们有两点要注意。第一点是,开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。第二点是,同样的代码改动,在粗代码粒度下,可能被认定为“修改”;在细代码粒度下,可能又被认定为“扩展”。
最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式
理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?
里氏替换原则:子类对象能够替换程序(program)中父类对象出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。
多态与里氏替换原则的区别:多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里式替换是一种设计原则,是用来指导继承关系中子类该如何设计的,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑以及不破坏原有程序的正确性。
理论四:接口隔离原则有哪三种应用?原则中的接口该如何理解?
接口隔离原则:客户端不应该强迫依赖它不需要的接口
接口隔离原则与单一职责原则的区别:单一职责原则针对的是模块、类、接口的设计。接口隔离原则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口来间接地判定。
理论五:控制反转、依赖反转、依赖注入,这三者有何区别和联系?
通过依赖注入的方式来将依赖的类对象传递进来,这样就提高了代码的扩展性,我们可以灵活地替换依赖的类
控制反转(IOC):框架提供了一个可扩展的代码骨架,用来组装对象、管理整个执行流程。程序员利用框架进行开发的时候,只需要往预留的扩展点上,添加跟自己业务相关的代码,就可以利用框架来驱动整个程序流程的执行。这里的“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员“反转”到了框架。
依赖注入(DI):不通过new()的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。
依赖反转原则(DIP):高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。
理论六:我为何说KISS、YAGNI原则看似简单,却经常被用错?
要去设计当前用不到的功能
不要去编写当前用不到的代码
不要做过度设计
不要使用同事可能不懂的技术来实现代码
不要重复造轮子,要善于使用已经有的工具类库
不要过度优化
KISS原则:尽量保持简单
YAGNI原则:你不会需要它
理论七:重复的代码就一定违背DRY吗?如何提高代码的复用性?
减少代码耦合
满足单一职责原则
模块化
业务与非业务逻辑分离
通用代码下沉
继承、多态、抽象、封装
应用模板等设计模式
代码的实现逻辑是相同的,但语义不同,我们判定它并不违反DRY原则
代码的实现逻辑不重复,但语义重复,也就是功能重复,我们认为它违反了DRY原则
代码中存在“执行重复”,违反了DRY原则
DRY原则:Don’t-Repeat-Yourself,不要写重复的代码
三种典型的代码重复情况:实现逻辑重复、功能语义重复和代码执行重复
提高代码复用性方法
RuleofThree:第一次编写代码的时候,我们不考虑复用性;第二次遇到复用场景的时候,再进行重构使其复用
理论八:如何用迪米特法则(LOD)实现高内聚、松耦合?
高内聚:就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一个类中
松耦合:在代码中,类与类之间的依赖关系简单清晰
高内聚低耦合
迪米特法则:不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口
实战一(上):针对业务系统的开发,如何做需求分析和设计?
面向对象设计聚焦在代码层面(主要是针对类),那系统设计就是聚焦在架构层面(主要是针对模块),两者有很多相似之处。
实战一(下):如何实现一个遵从设计原则的积分兑换系统?
分层能起到代码复用的作用
分层能起到隔离变化的作用
分层能起到隔离