面向对象设计原则

发布时间 2023-03-28 13:40:40作者: Linqylin
1、简介
常用的面向对象设计原则包括 7 个,这些原则并不是孤立存在的,它们相互依赖,相互补充。
SRP:就一个类而言,应该只有一个引起它变化的原因,也就是一个类只有一个职责,这个类只做一件事情,让一个类负责很多事情,就显得这个类很臃肿,不易复用。
OCP:对扩展开放,对修改关闭。应用程序写好了之后,因客户需求的变更,程序需要扩展功能。但是要保证不修改原来的代码,因为如果修改原来代码可能会导致意外的错误。
LSP:任何父类出现的地方,都可以被子类替换,并且替换之后,不会对程序产生影响。
DIP:高层不依赖于底层,调用者不应该依赖于被调用者,高层和底层都应该依赖于抽象。高层和底层都应面向接口或基于抽象类编程。
ISP:如果一个接口里有很多方法,那么在实现这个接口时,要实现它所有的方法。可能有些方法是用户不需要的,这时应该把这个接口拆分开,让一个接口包含尽量少的功能,然后让用户自己去选择,他应该去实现哪些功能。
LOP:也叫最少支持原则,也就是在不影响外部程序的情况下,让程序有最低的访问级别。
CARP:写程序时,如果需要代码复用,最好先使用组合和聚合的方法实现代码的复用,再考虑继承。
       这些设计原则会在潜移默化中影响我们对整个系统架构的设计和理解,同时也能够通过这些原则来衡量一个系统架构的好与坏。
 
(1)单一职责原则(Single Responsibility Principle)
       也就是说应该让一个类或一个对象只做一件事情,每个类所要关注的就是自己要完成的职责是什么,能够引起这个类变化的原因也应该只有一个,这也是所有设计模式都要遵守的一个原则。
        高内聚:按照面向对象的封装特性来理解,即应该把一个类或对象的所有相关属性、方法、行为放到一起,放到一个类当中,实现封装的特性。内聚,就是一个类里面应该包涵它所有的属性和行为。所以,一个类的属性或行为应该和这个类非常紧密,这样才把它们放到这个类里面,否则就不应该把这个属性或行为放到这个类里面。
       低耦合:内聚是指类的内部,耦合是指类与类之间或模块之间相互的联系,这种联系、关系叫耦合。衡量这种耦合的程度,可以用耦合度来表示,耦合度越高说明类与类之间的联系越紧密,相互之间的独立性也就比较差,也就是一个类必须依靠另一个类才有意义、才能存在。耦合度越低,越容易重用,类也比较灵活。
  注意:一个类与其他类间的依赖性越少越好。
  好处:高内聚,低耦合
单一职责原则总结:
   一个类(或者大到模块,小到方法)承担的职责越多,它被复用的可能性越小,而且如果一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。
   类的职责主要包括两个方面:数据职责和行为职责,数据职责通过其字段来体现,而行为职责通过其方法来体现。
   单一职责原则是实现高内聚、低耦合的指导方针,在很多代码重构过程中都能找到它的存在,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关重构经验。
 
(2)开闭原则(Open Closed Principle)
       开闭原则是指对功能扩展开放,对修改关闭。当用户需求发生变化时,如果必须要打开原来的代码进行修改,容易导致意外的错误,有时甚至会导致严重的问题。好的程序,应该保证在进行程序扩展时,不会或少更改以前的代码。
       如何才能做到这点?在最初编写程序时,假设变化不会发生。当变化发生时,就需要创建抽象或接口来隔离以后发生的同类变化,即应对程序中频繁变化的部分做抽象。这样在今后功能扩展时,只须要在原来抽象类和接口的基础上编写新的实现类和子类,这样既能扩展功能又不影响以前的功能!
  好处:
  适应性和灵活性好
  稳定性和延续性好
  可复用性与可维护性好
开闭原则总结:
   抽象化是开闭原则的关键。
 
(3)里氏替换原则(Liskov Substitution Principle)
  LSP 实际上是对开闭原则的一个扩展。在面向对象思想中,对象是由一系列的状态和行为组成的。里氏替换原则:在一个继承体系中,类应该具有共同的外在特性,以实现程序运行,当把父类全部替换为子类时,软件行为也没有变化。
  里氏替换原则本质:衡量父类是否包含了所有子类的共同部分。
  为什么说它是对开闭原则的一个扩展呢?因为在开闭原则中,要求尽量使用接口和抽象类,所以这个抽象类和接口也应该尽量定义得完整,这样接口和抽象类会比较稳定,符合了开闭原则,也满足了里氏替换原则。
  比如把海豚、企鹅当做宠物,可以定义一个宠物类。然后,让这些宠物继承这个类。由于每种宠物玩耍的方式是不一样的,比如,企鹅有游泳的方法,海豚有游戏的方法,因此企鹅类的游泳方法不能放到宠物类里面,因为并不是所有宠物都会游泳。同样编写海豚类时,海豚类的游戏方法也不能放到宠物类里面。
  当客户端程序在使用宠物类和它的子类时,就需要做判断具体是哪个子类,通过宠物类是无法调用具体的方法,而是要做一个判断和转型。(无法使用多态!)
 
(4)依赖倒置原则(Dependency Inversion Principle)
  DI 就是依赖倒置的意思,也可称为控制反转。在结构化的程序当中,如 C 语言,高层模块依赖于底层模块,是调用者和被调用者的关系。调用者要依赖于被调用者,被调用者编写的一些功能和服务,会影响高层,一旦底层发生了变化,就直接影响到高层。这样的设计,很难保证稳定性,经常会发生变化,代码维护起来也非常困难。
  但是在面向对象的设计中,底层和高层不应该有这样的依赖关系,高层不应该依赖于底层,底层也不应该依赖于高层,底层和高层都应该依赖于抽象类或接口。底层的变化,不影响高层。
  依赖倒置原则,本质上是在要求“面向接口编程”,它要求每个类尽量都来自接口或抽象类。
  开闭原则和依赖倒置原则是目标和手段的关系。开闭是目标,可以通过依赖倒置来实现对功能扩展开放,对修改关闭。
方法:对象注入
例子如下:

 

依赖倒转原则总结:
  简单来说,依赖倒转原则就是指:代码要依赖于抽象的类,而不要依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程。
  实现开闭原则的关键是抽象化,并且从抽象化导出具体化实现,如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段。
 
(5)接口隔离原则(Interface Segregation Principle)
  很多人都喜欢用一个接口把需要用到的方法全部声明出来,但是 ISP 建议我们使用多个专门的接口比使用单一的总接口要好,即一个接口包含很多方法的话,实现起来不是很方便。
  好处:比较灵活,方便,不想实现的或不用实现的可以不实现。
 
(6)迪米特原则
  LOP 原则也叫最少支持原则,即一个对象应当对其他对象尽可能少的了解,反过来,其他对象也应当尽量少的知道该对象。尽可能少的被其他对象所了解,通俗的讲就是不要跟陌生人说话。如果两个类不需要彼此通信,那么这两个类就不应该发生联系。
  当其中一个类须要调用另外一个类的方法时,可以通过中介类来实现,这样的好处就是类和类之间的耦合度比较低,比较容易扩展、灵活。模块设计好坏的一个很重要的标志,就是这个模块在多大程度上能把自己内部的实现隐藏起来,也就是在不影响使用的情况下尽量使用低的访问级别,访问级别也就是 Public、Private、Protected 等。如果把成员设置 Public 也就意味着,所有的类都可以对它进行访问,对它的使用者可能会增多,对它的修改可能会影响到更多的用户。
  好处就是降低耦合,不希望别人调用的成员,就可以使用低的访问级别,这样自然就降低了类与类之间的耦合度。
  但是如果过度的使用 LOP 原则,就会造成系统通信率降低,因为会产生大量的中介类,所以要把握一个度,凡事过犹不及!
 
(7)组合聚合复用原则
  一般地,当我们想复用代码时会优先想到继承,但是具有继承关系的两个类是耦合度最高的两个类。
  如果父类的功能比较稳定,建议使用继承方法来实现代码复用,因为继承是静态定义的,在运行时无法动态调用。
  组合:是整体与部分的关系,整体离不开部分,部分离开整体没有意义,如飞机翅膀与飞机的关系。
  聚合:也是整体与部分的关系,但整体可以离分部分,部分也可以离开整体,如火车和车厢的关系。
  组合/聚合:通过获得其他对象的引用,在运行时刻动态定义,也就是在一个对象中保存其他对象的属性。这种方式要求对象有良好定义的接口,并且接口也不经常发生改变,而且对象只能通过接口来访问。
例子如下: