极客时间第 0 期架构师训练营第二周总结

用户头像
2流程序员
关注
发布于: 2020 年 06 月 17 日

本周主要讲了不好的软件设计的几个特征和如何用一些原则来避免这些问题。

软件设计的“臭味”

好的软件应该是“强内聚、松耦合”的,主要体现在如下几点:

  1. 易扩展:添加新功能是容易实现的

  2. 更强壮:不会因为某一部分的错误导致其他部分出错

  3. 可移植:能够在多样的环境下运行

  4. 更简单:容易理解、容易维护

对应的,不好的软件会散发如下的“臭味”:

  1. 僵硬:添加新功能需要大面积的修改

  2. 脆弱:只想改名A,结构B被意外破坏

  3. 牢固性: 很难解开系统的耦合,组件难以重用

  4. 粘滞性:面临一个改动时,会破会原有设计

  5. 不必要的复杂性:设计了不会用到的结构

  6. 不必要的重复:复制、粘贴会增加大量的重复代码,当要修改时会涉及到大面积的重复修改

  7. 晦涩性:代码随着时间而演化,越来越晦涩难懂。

避免“发臭”的几个设计原则

开/闭原则(OCP)

对于扩展时开放的,对于修改时封闭的。简言之,不需要改变软件实体(类、模块、函数等),就应该能实现功能的扩展。

可通过一下几种模式来实现。

策略模式



如上图,实现一个计算器功能,只需将对应的实现类关联给调用方。

适配器模式

为每一类button实现一个具体的类,按下不同button时会调用不同的实现类。

观察者模式

被观察者维护观察者列表,当事件发生时,调用观察者中的制定方法

依赖倒置原则

原则描述:

  1. 高层模块不应该依赖低层模块,二者都依赖抽象。

  2. 抽象不应该依赖于细节,细节应该依赖于抽象。

解析:

  1. 所谓高层模块,是将一个或多个基础服务整合起来,实现某些具体业务流程的模块。

在高层模块不应该直接调用具体的基础服务类,因为这样,意味着具体业务流程需要依赖基础服务,在实际情况中一般是反过来的,业务流程决定基础服务。所以,为了保持扩展性,高层模块和底层基础服务都应该依赖接口或抽象类。

  1. 在Button控制Lamp的例子中,button如何检测是否被按下,lamp如何turnOn如何turnOff,这些细节都不是接口或抽象类所关注的事情,而是由具体的类来实现。只要保证在接口或抽象类规定的上下文中,高层模块就能正常运作。

好莱坞原则:

依赖倒置原则是框架设计的核心原则。

框架和工具的区别是:框架设计和实现了一整套流程并抽象了相应的接口,使用者只需要实现相应的接口即可实现相应的需求。

而工具则是一些已经实现了某些功能的类库或服务,使用者需要主动调用它们。

框架可以认为是高层模块,低层模块则是具体的业务功能。框架会自动调用使用者编写好的业务功能。

所以,依赖倒置原则又被称为好莱坞原则(Don't call me, we'll call you)

Liskov替换原则(LSP)

子类型必须能够替换掉它的基本类型

LSP是使OCP成为可能的主要原则之一,正是自类型的可替换性才使得使用基类类型的模块在无需改动的情况下可以扩展。

不符合“IS-A”关系的继承,一定不符合LSP

在实际使用过程中,不建议用继承的方式扩展类,优先选用组合方式。

也许是因为这个原则,golang在语言层面就没有设计明显的继承机制,而是建议采用组合的方式来编写代码。

单一职责原则(SRP)

就一个类而言,应该仅有一个引起它变化的原因。

上图中的rectangle的职责有两个,画图draw和计算面积area。

会导致脆弱性和不好可移植性的后果:

脆弱性:绘图和计算面积功能耦合在一起,当修改其中一个时,另一个可能也会意外所损。

不可移植性:只需要计算面积功能,却不得不把绘图功能和绘图功能依赖的组件包含进来。

接口分离原则(ISP)

不应该强迫客户依赖它们不用的方法

依然是上面的例子



上图中的rectangle的客户类有两个,而这两个客户类使用了它不同的方法。导致客户类引进了它们不需要的方法。这违背了ISP原则。

改进:





用户头像

2流程序员

关注

还未添加个人签名 2020.03.18 加入

还未添加个人简介

评论

发布
暂无评论
极客时间第 0 期架构师训练营第二周总结