OOD:SOLID设计原则
OOD:SOLID设计原则
SRP:单一职责原则(Single Responsibility Principle)
任何一个软件模块都应该有且仅由一个被修改的理由
"There should never be more than one reason for a class to change."
classDiagram class Employee{ +calculatePay +reportHours +save }
例如有一个员工类,包含由CFO需要的calculatePay(),COO需要的reportHours,CTO需要的save()。由于在一个类中,绝大多数程序猿会为避免代码重复,单独实现一个regularHours()函数。
graph TB calculatePay-->regularHourse reportHourse-->regularHourse
这种情况下,假设CFO团队要更改正常工作时数计算方法,而COO不需要更改,就会导致数据混乱。如果两个不同团队进行calculatePay和reportHorse的开发,合并代码会出现问题。
解决方案:
- 数据与函数分离,设计三个相互独立的类
graph LR PayCalculator-->E[Employee Data] HourReporter-->E EmployeeSaver-->E
- Facade设计模式
classDiagram class EmployeeFacade{ +calculatePay +reportHours +save } class PayCalculate{ +calculatePay } class HourReporter{ +reportHours } class EmployeeSaver{ +saveEmployee } EmployeeFacade-->PayCalculate EmployeeFacade-->HourReporter EmployeeFacade-->EmployeeSaver PayCalculate-->EmployeeData HourReporter-->EmployeeData EmployeeSaver-->EmployeeData
OCP:开闭原则(Open-Closed Principle)
设计良好的计算机软件应该易于拓展,同时抗拒修改
or
一个设计良好的计算机软件应该在不需要修改的前提下就可以轻易地被拓展
如果A组件不想被B组件上发生的修改所影响,那么就应该让B组件依赖A组件
个人理解就是尽量通过接口隐藏细节,使上层的变动对下层的影响降到最小,最好是没有影响。例如:不论服务器采用的是IIS还是Nginx,都应该对同样的指令做出相同的回应
LSP:里式替换原则(Liskov's Substitution Principle)
"Subtypes must be substitutable for their base types."
子类必须能够替代他们的基类。
例如,定义一个鸟的基类,包含fly()方法。那么翠鸟可以继承这个鸟的基类,但是企鹅或者鸵鸟就不能继承,在现实世界企鹅和鸵鸟也是鸟没有问题,但是他们不能飞,所有违反了LSP原则。
正确使用:
classDiagram class License{ +calcFee() } class BusinessLicense{ -users } Billing-->License PersonalLicense--|>License BusinessLicense--|>License
billing应用程序的行为并不依赖于其使用的任何一个衍生类,即这两个衍生类的对象都是可以用来替换License类对象的。
错误:正方形/长方形问题
classDiagram class Rectangle{ +setH +setW } class Square{ +setSide } User-->Rectangle Rectangle<|--Square
虽然正方形是长方形,但是长方形可以单独设置长宽,而正方形不可以,违反了LSP原则。加if可以解决这个问题,但是两个类不能相互替换了。
ISP:接口隔离原则(The Interface Segregation Principle)
"Clients should not be forced to depend upon interfaces that they do not use."
客户端不应该被迫依赖于它们不用的接口
例如有多个用户需要操作OPS类,但是User14只需要使用ops1,User2只需要使用ops2,User3只需要使用ops3,如果设计成下图,即使对OPS中ops2做任何修改,即使不会影响User1的功能,也会导致需要重新编译和部署。
classDiagram User1-->OPS User2-->OPS User3-->OPS class OPS{ +ops1 +ops2 +ops3 }
但如果改成改成下图形式,对OPS的修改只要不影响到User1的功能,就不需要重新编译和部署User1了。
classDiagram User1-->U1ops User2-->U2ops User3-->U3ops U1ops<|--OPS U2ops<|--OPS U3ops<|--OPS class U1ops{ +ops1 } class U2ops{ +ops2 } class U3ops{ +ops3 } class OPS{ +ops1 +ops2 +ops3 }
需要注意的是U1ops、U2ops、U3ops都是接口,java可以通过一个接口继承多接口实现,C++没有接口的概念可以通过多重继承实现。
任何层次的软件依赖了它本身并不需要的东西,就会带来意料之外的麻烦
DIP:依赖反转原则(The Dependency Inversion Principle)
"High level modules should not depend upon low level modules. Rather, both should depend upon abstractions."
高层模块不应该依赖底层模块,两者都应该依赖其抽象。
几条守则:
- 应在代码中多使用抽象接口,尽量避免使用那些多变的具体实现类。
- 不要在具体实现类上创建衍生类。
- 不要覆盖(override)包含具体实现的函数。
- 应该避免在代码中写入与任何具体实现相关的名字,或者是其他容易变动的事物的名字。
C++可以通过vitual纯虚函数实现。
参考资料
- How-I-explained-OOD-to-my-wife
- 架构整洁之道.Clean.Architecture