面向对象设计原则

1. 重新认识面向对象

  • 从宏观层面来看,面向对象的构建方式更能适应软件的变化,能将变化所带来的影响减为最小

  • 各司其职

    • 从微观层面来看,面向对象的方式强调各个类的 “责任”
    • 由于需求变化导致的新增类型不应该影响原来类型的实现---- 是所谓各负其责
  • 对象是什么?

    • 从语言实现层面,对象封装了代码和数据。
    • 从规格层面,对象是一系列可被使用的公共接口
    • 从概念层面,对象是某种拥有责任的抽象
  • 变化是复用的天敌,面向对象设计最大的优势在于:抵御变化

2. 面向对象设计原则

  • 所有的设计模式都是依赖于设计原则推导出来的,违背了设计原则,那么这个模式就是错误的,设计原则就是一把尺子

2.1 依赖倒置原则(DIP)

  • 高层模块(稳定)不应该依赖于底层模块(变化),二者都应该依赖于抽象(稳定)。
  • 抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)
  • 该原则基本贯穿了整个设计模式
  • 核心在于提炼出抽象类,高层模块依赖抽象类,而具体实现细节也依赖抽象类。
  • 类似于嵌入式领域中的驱动框架的感觉,各种乱七八糟的驱动依赖于驱动框架,需要将自己的驱动注册至驱动框架中,高层应用层依赖驱动框架提供的统一接口实现应用,而驱动框架就是抽象类。当底层新增驱动时,如一个 i2c 的心率传感器,将底层驱动程序注册至驱动框架中,应用层直接还是按照原先的接口去实现应用。比如 rt-thread 中的驱动框架、linux 中的各种字符设备、块设备等等驱动框架,提供好了接口,等着驱动工程师按照一定的规矩将测试好的驱动注册到驱动框架中。

2.2 开放封闭原则(OCP)

  • 对扩展开放,对更改封闭
  • 类模块应该是可扩展的,但是不可修改
  • 当需求变更的时候,不要想着去改源代码,而是想着增加一些东西应对需求的变化
  • 在嵌入式中写好了一个框架,比如异常管理框架,为了新增一个异常模块,比如 asan 模块,仅需要新增一个 asan 文件,然后根据异常框架提供的注册机制去注册,剩余的完全不用管,当系统异常时,异常框架自动调用扩展的异常模块
  • 好处在于不用更改源代码,一旦改了源代码,就需要对源代码重新编译、测试,代价非常高。

2.3 单一职责原则(SRP)

  • 一个类应该仅有一个引起它变化的原因。
  • 变化的方向隐含着类的责任。
  • 类中不应该放太多成员太多功能,这样的类隐含了多个责任,这样多个责任会将该类向多个方向拉扯,这样会出毛病

2.4 Liskov 替换原则(LSP)

  • 子类必须能够替换它们的基类(IS-A)
  • 继承表达类型抽象
  • 所有需要父类的地方,子类传过去都可以使用
  • 子类应该继承合适的父类,不应该继承与自己没啥关系的父类

2.5 接口隔离原则(ISP)

  • 不应该强迫客户程序依赖它们不用的方法
  • 接口应该小而完备
  • 不要把不必要的方法 public 出去,无节制做成 public 很容易让外部程序(客户程序,使用它的程序)产生依赖,一旦产生依赖,就需要让暴漏出去的接口保持稳定

2.6 优先使用对象组合,而不是类继承

  • 类继承同行为 “白箱复用”,对象组合通常为 “黑箱即用”(class A 里面放个 class B 或者一个 class B 的指针)
  • 继承在某种程度上破坏了封装性,子父类耦合度度高
  • 而对象组合则要求被组合的对象具有良好定义的接口,耦合度低。

2.7 封装变化点

  • 使用封装来创建对象之间的分层,让设计者可以在分解的一侧进行修改,而不会对另一侧产生不良影响,从而实现层次间的松耦合。
  • 应该在软件领域划分出一些所谓的分解层,封装高层次的理解是封装变化点,一侧变化,一侧稳定

2.8 针对接口编程,而不是针对实现编程

  • 不将变量类型声明为某个特定的具体类,而是声明为某个接口。
  • 客户程序获知对象的具体类型,只需要知道对象所具有的接口。
  • 减少系统中各部分的依赖关系,从而实现 “高内聚、松耦合” 的类型设计方案。

你可能感兴趣的:(软件设计,设计模式)