- 单一职责原则:任何一个软件模块都应该只对某一类行为者负责
怎么理解“某一类行为者”?我觉得应该是调用方,软件模块的使用者,也就是说任何一个软件模块都应该只有一类调用方/使用者,如果存在多个种类的调用方即违反了SRP。
举个《架构整洁之道》里面的反例:
classDiagram
Employee <.. CFO
Employee <.. COO
Employee : +Long calculatePay()
Employee: +int reportHours()
class CFO{
+Long calculatePay()
}
class COO{
+int reportHours()
}
CFO和COO都需要用到雇员类的calculatePay和reportHours(注意:CFO计算工资要先看工时,即calculatePay调用了commonHours),这时候一个类就有两个使用者,那会有什么危害吗?
flowchart LR
calculatePay & reportHours--> commonHours
假设现在CFO需要对工时计算逻辑进行调整,负责修改这项的程序员,需要修改calculatePay所调用的commonHours逻辑,但是可能不会注意到commonHours还被reportHours调用了,这时候上线就发现COO的报表出问题了。
怎么解决?
把calculatePay和reportHours方法单独提出来创建一个类封装好,但是这样的坏处就是程序员需要关注的“和员工相关操作”不在一个同一处地方,让代码不太易理解(因为拆太细了),所以这时候可以使用门面模式,把创建出来的两个类组合在一个门面类中,这个门面类就对外提供员工相关的操作,整体结构如下:
classDiagram
EmployeeFacade <.. CFO
EmployeeFacade <.. COO
EmployeeFacade : +PayCalculator
EmployeeFacade: +HourReporter
PayCalculator <.. EmployeeFacade
HourReporter <.. EmployeeFacade
class PayCalculator{
+Long calculatePay()
}
class HourReporter{
+int reportHours()
}
class CFO{
+Long calculatePay()
}
class COO{
+int reportHours()
}
-
开闭原则:对于需要实现新需求的情况,设计良好的软件,应该是能使用扩展的方式,而少/甚至不修改
-
里氏替换原则:不同子类可以互相替换
-
接口隔离原则:以定义接口的形式来让我们多个功能之间隔离,而使用方需要面向接口编程 为什么要这样进行接口隔离? 其实是为了调用方不要依赖不需要依赖的东西,降低编程的复杂度(你要关注的东西越多,就越复杂)。
假设现在有个需求,需要对家里的所有动物进行跑步比赛和讲话比赛。
classDiagram
Cat --|> Animal
Dog --|> Animal
Animal <.. RunningRace
Animal <.. SpeakingRace
Animal : +void speak()
Animal: +void run()
class Cat{
+void speak()
+void run()
}
class Dog{
+void speak()
+void run()
}
class SpeakingRace{
+void start()
}
class RunningRace{
+void start()
}
可以发现,无论是跑步比赛还是讲话比赛,都不能完全只关注一个点(跑步比赛还要关注动物的讲话能力),赛事的举办方可能就会怀疑跑步和讲话能力是不是有关联,即使这两根本没有关系。
为了让调用方不关注到其他不需要的东西,就需要在编程的时候多考虑接口隔离,对这个案例,可以抽出Speak 和 Run两个接口,跑步比赛只依赖Run接口,讲话比赛只依赖Speak接口。
classDiagram
Cat --|> Animal
Dog --|> Animal
Animal --|> Run
Animal --|> Speak
Run <.. RunningRace
Speak <.. SpeakingRace
Animal : +void speak()
Animal: +void run()
class Speak{
<<interface>>
+void speak()
}
class Run{
<<interface>>
+void run()
}
class Cat{
+void speak()
+void run()
}
class Dog{
+void speak()
+void run()
}
class SpeakingRace{
+void start()
}
class RunningRace{
+void start()
}
- 依赖倒置原则:依赖抽象,不依赖具体实现
这几个原则主要是代码层面的,并且基本上都是说调用者和实现方法不要强依赖,怎么做到?需要将调用者和实现方法的依赖倒置,调用者直接依赖抽象接口,而实现方法也依赖于抽象接口定义实现,并且从调用者角度看,类暴露的接口得准确得当,不要把当前调用者不需要看到的东西也展示出来,在类/方法定义上,要满足单一职责原则,起码在定义类和方法的时候不要把不是那么确定不会变的东西复用,该拆开成两个方法、两个类就拆,不要大而全,后面维护起来会很痛苦的
如果把眼光再往上一层,到组件级别,就不是说只考虑降低依赖即可,还需要考虑组件维护难度如何、是不是拆太细了、能不能开箱即用