面向对象的七个原则

总结:

  • 单一职责原则告诉我们实现类要职责单一;
  • 里氏替换原则告诉我们不要破坏继承体系;
  • 依赖倒置原则告诉我们要面向接口编程;
  • 接口隔离原则告诉我们在设计接口的时候要精简单一;
  • 迪米特法则告诉我们要降低耦合;
  • 而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭;
  • 合成复用原则告诉我们要优先使用组合或者聚合关系复用,少用继承关系复用;
      

1. 单一职责原则(SRP)

  • 定义:

    • 让一个类只承担一种类型责任,当这个类需要承当其他类型的责任的时候,就要分解这个类
        
  • 一句话:

    • 高内聚低耦合的绝佳体现,不要乱拉关系,独善其身挺好
        
  • 目的:

    • 实现模块化,即高内聚、低耦合
        
  • 好处:

    • 降低类的复杂度,提高类的可读性
    • 控制变更影响的范围
    • 综合以上两点,可提高系统的可维护性
        
  • 例子

2. 开放封闭原则(OCP)

  • 定义:
    • 一个软件实体(如类、模块、函数)应该对扩展开放,对修改封闭

    • 通俗来说,就是当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化

    • 注:抽象化是开闭原则的关键
        

  • 一句话
    • 开放扩展,封闭更改,开合有度是一门艺术
        
  • 目的 / 好处:
    • 提高程序的可重用性
        Why?--> 若应对需求的变更,都是对原有的类进行修改,则可能使原有的类累积过多的功能,则不利于功能重用
        
    • 提高程序的可维护性
        Why?--> 采用新增代码的方式应对新需求,可降低修改原有代码带来的难度;避免引入新错误;降低测试难度

3. 里氏代换原则(LSP)

  • 定义:

    • 用子类对象替换掉父类对象后,软件功能不受影响
    • 注:里氏代换原则是实现开闭原则的重要方式之一
        
  • 一句话

    • 长辈给了你继承的权利就一定要做赡养的义务,把长辈的职责都要承担起来
        
  • 目的:

    • 里氏代换原则是继承复用的基石。只有符合里式代换原则,基类才能真正被复用,而派生类也才能够在基类的基础上增加新的功能
        
  • 好处:

    • 基类能真正被复用
    • 派生类能够在基类的基础上增加新的功能
        
  • 违反时如何重构

    • 创建新的抽象类 C,作为两个类的基类
    • 从B到A的继承关系改为委派关系
      
  • 例子

4.接口隔离原则(ISP)

  • 定义:

    • 使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口
        
    • 可以将 “接口” 理解为一个类所具有的公有方法的集合
    • 一个接口相当于剧本中的一种角色,而此角色在一个舞台上由哪一个演员来演则相当于接口的实现。因此,一个接口应当简单地代表一个角色,而不是多个
    • 我们将这种角色划分的原则叫做角色隔离原则
        
  • 一句话

    • 做好自己该做的事,不管闲事
        
  • 目的:

    • 每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干
        
  • 好处:

    • 降低接口的复杂度,提高接口的可读性和整洁程度
    • 提高系统的可维护性
        
  • 补充

    • 接口污染:
      • 过于臃肿的接口是对接口的污染
          
    • 定制服务:
      • 如果客户端仅仅需要某一些方法的话,就应当向客户端提供这些需要的方法,而不要提供其不需要的方法
          
  • 例子

5. 依赖倒换原则(DIP)

  • 定义:

    • 抽象不应当依赖于细节,细节应当依赖于抽象
    • 另一种表述是:要针对接口编程,不要针对实现编程
        
  • 一句话

    • 搞建筑时要做设计师,而不是砖瓦工,抽象的蓝图要靠具体的材料一点点实现
        
  • 目的:

    • 传统的面向过程的设计办法倾向于使高层次的模块依赖于低层次的模块;抽象层次依赖于具体层次。而 DIP 就是要把这个错误的依赖关系倒转过来
        
  • 好处:

    • 实现抽象层次模块的复用,而不是具体层次模块的复用
    • 较低层次的修改不会影响较高层次的修改
    • 提高系统的复用性和可维护性
        
  • 缺点

    • 导致生成大量的类
    • 如果一个具体类发生变化的可能性很小,那么抽象耦合能发挥的好处便十分有限,这时使用具体耦合反而会更好
        
  • 怎样做到

    • 以抽象方式耦合是依赖倒转原则的关键。 使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。
        由于一个抽象耦合关系总要涉及到具体类从抽象类继承,并且保证在任何引用到基类的地方都可以转换成其子类,因此,里氏代换原则是依赖倒转原则的基础。
        
  • 例子

      
    • 在这个例子里,Account 类依赖于 AccountType 这个抽象类型,而不是它的子类型
      Account 类并不依赖于具体类,因此当有新的具体类型添加到系统中时, Account 类不必改变
    • 例如,如果系统引进了一个新型的账号:MoneyMarket 类型,Account 类以及系统里面所有其他的依赖于 AccountType 抽象类的客户端均不必改变
        
  • 补充

    • 三种耦合关系
      1.零耦合:如果两个类没有耦合关系,就称为零耦合
      2.具体耦合:具体耦合发生在两个具体的(可实例化的)类之间,经由一个类对另一个具体类的直接引用造成的
      3.抽象耦合:抽象耦合关系发生在一个具体类和一个抽象类(或者Java接口)之间,使两个必须发生联系的类之间存有最大的灵活性
        
    • 变量的静态类型和真实类型
        变量被声明时的类型叫做变量的静态类型,变量所引用的对象的真实类型叫做变量的实际类型
        只要一个被引用的对象存在抽象类型,就应当在任何引用此对象的地方使用抽象类型,包括变量的类型声明,方法返还类型的声明,属性变量类型的声明等。 简而言之,在声明类型的时候,尽量使用抽象类
        
    • 示例
      List employees = new Vector();
      
        上述代码中,employees变量的静态类型是List,而它实际类型是Vector
        
        尽量不要使用:
      1- Vector employees = new Vector();
      
        应当使用:
      2- List employees = new Vector();
      
        区别:前者 (1) 使用具体类作为变量的类型,而后者 (2) 使用一个抽象类(List 是 Java 接口)作为类型
        
        好处:在决定将 Vector 类型转换成 ArrayList 时,需要改动的很少:
      List employees = new ArrayList();
      

6. 组合/聚合复用原则(CARP)

  • 定义:
    • 在一个新的对象里面使用一些已有的对象,使之(即已有对象)成为新对象的一部分,新的对象通过向这些已有对象的委派,达到复用已有功能的目的
    • 简短的描述:要尽量使用组合、聚合,尽量不要使用继承
        
  • 一句话
    • 优生优育,不要盲目繁衍
        
  • 目的:
    • 实现已有设计和实现
        
  • 好处:
    • 这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的,容易实现封装
    • 这种复用所需的依赖较少
    • 这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的其它对象
        
  • 缺点
    • 会有较多的对象需要管理
        
  • 补充
    • 组合/聚合
      组合:一种强关联,整体部分同生共死(水和鱼)
      聚合:一种弱关联,部分可独立于整体而存在
        
    • 实现复用的两种基本方式
      组合/聚合 继承
      使用环境 可以应用到几乎任何环境中 只能在有限的环境中使用
      优点 成分对象的内部细节是不可见的,容易实现封装;所需的依赖较少;可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的其它对象。 新的实现较为容易,因为超类的大部分功能可以通过继承的关系自动进入子类;容易修改和扩展继承而来的实现。
      缺点 会有较多的对象需要管理。 破坏封装;如果超类发生改变,那么子类的实现也不得不发生改变;从超类继承而来的实现是静态的,不可能在运行时间内发生改变,没有足够的灵活性。
      
    • 区分 “Is-A” 和 “Has-A”
      “Is-A” 代表一个类是另外一个类的一种
      “Has-A” 代表一个类是另一个类的一个部分,而不是另一个类的特殊种类
      对于 "Is-A" 应该考虑使用继承,而 "Has-A" 使用组合/聚合
        
    • 如果两个类的关系是 “Has-A” 而不是 “Is-A” 关系,这两个类一定违反里氏代换原则。
    • 只有两个类满足里氏代换原则,才有可能是 “Is-A” 关系
        
  • 例子

7. 迪米特法则(LoD)

  • 定义:一个软件实体应当尽可能少地与其他实体发生相互作用。又称为最少知识原则(一个对象应当对其他对象尽可能少的了解)
      
  • 一句话
    • 不要和陌生人说话,若两国交战要尽量避免正面冲突,多派使者协商调度
        
  • 目的 / 好处:
    • 当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易
    • 降低系统的耦合度,使类与类之间保持松散的耦合关系
        
  • 补充
      应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度
      
  • 例子
posted @ 2020-11-06 18:40  物转星移几度秋  阅读(130)  评论(0)    收藏  举报