Skip to main content

面向对象设计原则

一. 单一职责原则SRP​

所谓单一职责原则(Single Responsibility Principle),指的是,一个类应该仅有一个引起它变化的原因。

这里变化的原因就是所说的“职责”,如果一个类有多个引起它变化的原因,那么也就意味着这个类有多个职责,再进一步说,就是把多个职责耦合在一起了。

这会造成职责的相互影响,可能一个职责的变化,会影响到其他职责的实现,甚至引起其他职责随着变化,这种设计是很脆弱的。

这个原则看起来是最简单和最好理解的,但是实际上是很难完全做到的,难点在于如何区分“职责”。这是个没有标准量化的东西,哪些算职责、到底这个职责有多大的粒度、这个职责如何细化等。因此,在实际开发中,这个原则也是最容易违反的。

二. 开放-关闭原则OCP​

所谓开放一关闭原则(Open-Closed Principle),指的是,一个类应该对扩展开放,对修改关闭。一般也被简称为开闭原则,开闭原则是设计中非常核心的一个原则。

开闭原则要求的是,类的行为是可以扩展的,而且是在不修改已有代码的情况下进行扩展,也不必改动已有的源代码或者二进制代码。

看起来好像是矛盾的,怎么样才能实现呢?

实现开闭原则的关键就在于合理地抽象、分离出变化与不变化的部分,为变化的部分预留下可扩展的方式,比如,钩子方法或是动态组合对象等。

这个原则看起来也很简单。但事实上,一个系统要全部做到遵守开闭原则,几乎是不可能的,也没这个必要。适度的抽象可以提高系统的灵活性,使其可扩展、可维护,但是过度地抽象,会大大增加系统的复杂程度。应该在需要改变的地方应用开闭原则就可以了,而不用到处使用,从而陷入过度设计。

三. 里氏替换原则LSP​

所谓里氏替换原则(Liskov Substitution Principle),指的是,子类型必须能够替换掉它们的父类型。这很明显是一种多态的使用情况,它可以避免在多态的应用中,出现某些隐蔽的错误。

事实上,当一个类继承了另外一个类,那么子类就拥有了父类中可以继承下来的属性和操作。理论上来说,此时使用子类型去替换掉父类型,应该不会引起原来使用父类型的程序出现错误。

但是,很不幸的是,在某些情况下是会出现问题的。比如,如果子类型覆盖了父类型的某些方法,或者是子类型修改了父类型某些属性的值,那么原来使用父类型的程序就可能会出现错误,因为在运行期间,从表面上看,它调用的是父类型的方法,需要的是父类型方法实现的功能,但是实际运行调用的却是子类型覆盖实现的方法,而该方法和父类型的方法并不一样,于是导致错误的产生。

从另外一个角度来说,里氏替换原则是实现开闭的主要原则之一。开闭原则要求对扩展开放,扩展的一个实现手段就是使用继承;而里氏替换原则是保证子类型能够正确替换父类型,只有能正确替换,才能实现扩展,否则扩展了也会出现错误。

四. 依赖倒置原则DIP​

所谓依赖倒置原则(Dependence Inversion Principle),指的是,要依赖于抽象,不要依赖于具体类。要做到依赖倒置,典型的应该做到:

  • 高层模块不应该依赖于底层模块,二者都应该依赖于抽象。
  • 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。

很多人觉得,层次化调用的时候,应该是高层调用“底层所拥有的接口”,这是一种典型的误解。事实上,一般高层模块包含对业务功能的处理和业务策略选择,应该被重用,是高层模块去影响底层的具体实现。

因此,这个底层的接口应该是由高层提出的,然后由底层实现的。也就是说底层的接口的所有权在高层模块,因此是一种所有权的倒置。

倒置接口所有权,这就是著名的Hollywood(好莱坞)原则:不要找我们,我们会联系你。

五. 接口隔离原则ISP​

所谓接口隔离原则(Interface Segregation Principle),指的是,不应该强迫客户依赖于他们不用的方法。

这个原则用来处理那些比较“庞大”的接口,这种接口通常会有较多的操作声明,涉及到很多的职责。客户在使用这样的接口的时候,通常会有很多他不需要的方法,这些方法对于客户来讲,就是一种接口污染,相当于强迫用户在一大堆“垃圾方法”中去寻找他需要的方法。

因此,这样的接口应该被分离,应该按照不同的客户需要来分离成为针对客户的接口。这样的接口中,只包含客户需要的操作声明,这样既方便了客户的使用,也可以避免因误用接口而导致的错误。

分离接口的方式,除了直接进行代码分离之外,还可以使用委托来分离接口,在能够支持多重继承的语言中,还可以采用多重继承的方式进行分离。

六. 最少知识原则LKP​

所谓最少知识原则(Least Knowledge Principle),指的是,只和你的朋友谈话。

这个原则用来指导我们在设计系统的时候,应该尽量减少对象之间的交互,对象只和自己的朋友谈话,也就是只和自己的朋友交互,从而松散类之间的耦合。通过松散类之间的耦合来降低类之间的相互依赖,这样在修改系统的某一个部分的时候,就不会影响其他的部分,从而使得系统具有更好的可维护性。”

那么究竟哪些对象才能被当作朋友呢?最少知识原则提供了一些指导。

  • 当前对象本身。
  • 通过方法的参数传递进来的对象。
  • 当前对象所创建的对象。
  • 当前对象的实例变量所引用的对象。
  • 方法内所创建或实例化的对象。

总之,最少知识原则要求我们的方法调用必须保持在一定的界限范围之内,尽量减少对象的依赖关系。

七. 其它原则​

除了上面提到的这些原则,我们还应该遵循以下原则:

  1. 面向接口编程:优先使用接口而不是具体实现类来编程,以提高系统的灵活性和可扩展性。
  2. 优先使用组合,而非继承:通过组合多个对象来实现功能,而不是通过继承来扩展类的功能,以减少类之间的耦合。
  3. 信息隐藏:一个类需要的数据应该隐藏在类的内部,避免外部直接访问类的内部数据。
  4. 零耦合或传导耦合:类之间应该尽量没有关系,或者只使用另一个类的接口提供的操作。
  5. 水平分布系统功能:在水平方向上尽可能统一地分布系统功能,以提高系统的可维护性和可扩展性。

八. 微服务设计原则​

除了上面的传统OOP设计的遵循原则外,针对当下流行的微服务系统的设计,还应该遵循以下原则:

  1. 单一职责原则(SRP):每个微服务应该只负责一个特定的业务功能,避免职责过多导致的复杂性。
  2. 自治性:每个微服务应该是独立的,能够独立部署和运行,不依赖于其他服务。
  3. 去中心化治理:尽量减少中心化的管理和协调,允许各个微服务团队独立决策和开发。
  4. 容错性:设计微服务时要考虑到故障的发生,确保服务在部分组件失效时仍能继续运行。
  5. 自动化:尽量自动化部署、监控和恢复过程,以提高系统的可靠性和可维护性。
  6. 接口契约:定义清晰的服务接口和契约,确保服务之间的通信稳定可靠。
  7. 数据隔离:每个微服务都应该拥有自己的数据存储,避免直接共享数据库。
  8. 弹性伸缩:设计微服务时要考虑到负载的变化,能够根据需求自动扩展或缩减资源。
  9. 日志和监控:实现全面的日志记录和监控,及时发现和解决问题。
  10. 安全性:确保微服务之间的通信安全,保护数据隐私和防止未授权访问。