设计原则
时刻保持动态的眼光来看待这些设计原则
SRP 单一职责原则
SRP,Single Responsibility Principle. 这个原则的核心是:一个类或者模块只负责完成一个职责(或功能)。
解读:
SRP 并不好把控,没有一个放之四海而皆准的标准;有时候也理解为只有一个原因引起类或模块的变化,都非常抽象和模糊,并不具体,所以 SRP 真的去落地到实际项目就会有难度;所以,职责划分是 SRP 最难的,如何找到职责边界,如何落地,在不同的情境下对职责的理解和考量又不尽相同
不同需求阶段,不同应用场景,不同业务角度去理解和应用 SRP 都是不一样的,这个需要通过大量的实战来总结经验,找到 SRP 的“感觉”;但是可以采取持续重构的方式来应对 SRP,尽量让模块和类的设计满足它,可以作为设计时的指导原则和意识。
在实际应用中,不要设计大而全的类,要设计粒度小、功能单一的类,如果某个类或模块承担了过多的职责,那就可以考虑将它拆分成多个类或模块;可以说 SRP 是高内聚的一种体现或指导原则,把相同的功能或职责内聚到一个类或模块中,不同类和模块之间以一种代价最小的方式组装在一起,类和类、模块和模块之间必定存在联系,如果没有联系的两件东西放在一起毫无价值,而这种联系的强弱和稳定度是需要重点考虑的,SRP 是从职责和功能的角度来界定内聚的标准和耦合的尺度,告诉我们做设计时要从自己理解的角度去做需求拆分、功能拆分、子系统拆分、模块拆分、类的拆分、方法的拆分,而不是用一个大而全的东西包含所有的,拆分能带来职责明晰,多方协作,这不正是真实世界的运作机制吗
其实,我很想能思考明白 SRP 的本质是什么,但是它确实太抽象太模糊,我认为的如何应用 SRP 的一般方法:
首先要有 SRP 的意识,总结为意识先行;
其次善于利用持续重构的方式,不要做过度设计,做符合当前需求和情景的设计,尽量保持类或模块的小粒度和功能单一,总结为重构开路;
最后总结经验,培养感觉,在实战中提升,总结为积累经验;
SRP 意识先行,持续重构开路,实战中积累经验。
简单理解 SRP 就是要从职责的层面做到分工明确。
OCP 开闭原则
OCP 说的是对软件实体(模块、类、方法等)应该对扩展开放,对修改关闭。
OCP 并不是说不需要修改代码,而是从软件设计和功能语义层面去理解,要认识到,添加一个新功能,不可能任何模块、类、方法的代码都不“修改”,这个是做不到的。类需要创建、组装、并且做一些初始化操作,才能构建成可运行的的程序,这部分代码的修改是在所难免的。我们要做的是尽量让修改操作更集中、更少、更上层,尽量让最核心、最复杂的那部分逻辑代码满足开闭原则。比如说骨架需要是稳定的,这里就不要做过多修改,而枝叶是多种多样的,这里就要做好灵活的扩展机制了。再通俗点的理解:我们有一个稳定的结构,在这个基础之上可以支持尽可能多的扩展能力,扩展能力就是对扩展开放,稳定结构就是对修改关闭。举个例子,CPU 大家都了解,CPU 的设计就符合 OCP,CPU 分为计算单元、控制单元、数据单元和一系列指令,这个结构是稳定的,CPU 进化了那么多年依然是这么工作的,但是 CPU 的能力可以无限扩展的,CPU 可以处理文本数据、图像、音视频等等,但是都不需要修改 CPU 本身。
OCP 是一个指导原则,指导我们设计出有稳定结构且可以无限扩展的架构,更好的将可变部分和不变部分隔离开。做到 OCP 不是一件容易的事情:
首先要有抽象、封装、扩展的意识,有这些意识是做好事情的前提;就是在做的时候,花点时间去多思考,不同角度反复推敲,比如未来哪些需求可能会变,是不是要留扩展点,代码结构怎么设计,单测怎么设计,会不会严重违反某些好的指导原则等
多态、依赖注入、基于接口而非实现编程、抽象等都是实现 OCP 的具体方式
一些常见的设计模式也可以做到 OCP
为什么我们要“对扩展开放、对修改关闭”?
做任何事情都需要进行积累,从0到1,从1到2,从2到3...这样一步步的完成我们的目标,这个过程中就需要有一个稳定的地基来承载每一步带来的变化,当然地基本身的稳定性也是一点点重构出来的,随着理解的加深,地基就越来越稳定,新的需求在已有的功能上进行迭代,这是我们追求的最好的样子,不浪费以前的工作,继续开展新的工作,日积月累完成最终的目标。所以 OCP 就是这样一种指导原则,提醒我们在做事情时要识别稳定点和变化点,将稳定点满满的沉淀为稳定的地基,将变化点演变为灵活的扩展点,只有这样才是最高效的。这样我们就可以利用抽象意识,多态、依赖注入、面向接口编程等很多方式去落地代码,达到不浪费以前工作,继续基础新功能的目的。
LSP 里式替换原则
LSP 是这样描述的:子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。
LSP 有两个关键点:
子类可以替换父类
要保证原来程序的逻辑行为(behavior)不变及正确性不被破坏
时刻抓住这两点,就能很好判断一段程序或者一个设计是否符合 LSP。如果子类替换父类后产生了协议之外的副作用,就说明违背了 LSP,可能会造成很多未知的错误,或者引发其他系统的错误。
LSP 告诉我们一定要严格按照协议设计来实现,LSP 可以用于接口设计、类设计、方法设计等。
此外,多态和里式替换有点类似,但它们关注的角度是不一样的。多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里式替换是一种设计原则,是用来指导继承关系中子类该如何设计的,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑以及不破坏原有程序的正确性。
ISP 接口隔离原则
ISP 是这样描述的:客户端不应该强迫依赖它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。
ISP 的核心是来指导接口的设计,告诉我们要结合具体使用场景,侧重于如何设计接口才是更加合理的,ISP 给了一个判断标准,如果使用者只依赖接口的部分功能,就需要把接口的功能拆分成多个接口,进行细化;如果使用者只依赖部分接口,那就需要把其他接口功能做隔离,放到其他接口的上下文中
最后更新于