Java面试官问:“面向对象7大设计原则”——教你如何完美解答

7大设计原则如何分类

Java面试官问:“面向对象7大设计原则”——教你如何完美解答

七大原则之间并不是相互孤立的,彼此间存在着一定关联,一个可以是另一个原则的加强或是基础。违反其中的某一个,可能同时违反了其余的原则。开闭原则是面向对象的可复用设计的基石。其他设计原则是实现开闭原则的手段和工具。

一般地,可以把这七个原则分成了以下两个部分:

设计目标:开闭原则、里氏代换原则、迪米特法则。

设计方法:单一职责原则、接口分隔原则、依赖倒置原则、组合/聚合复用原则。

其中,单一职责原则(Single Responsibility Principle,SRP)、开闭原则(The Open-Closed Principle, OCP)、里氏代换原则(Liskov Substitution Principle,LSP)、接口分隔原则(Interface Segregation Principle,ISP)、依赖倒置原则(Dependency Inversion Principle,DIP)合称为SOLID原则,也是设计原则中最主要的部分,另外两个原则作为补充。

开闭原则

开闭原则(The Open-Closed Principle,OCP)是指在进行面向对象设计中,设计类或其他程序单位时,应该遵循对扩展开放(open)、对修改关闭(closed)的设计原则。

开闭原则是判断面向对象设计是否正确的最基本的原理之一。

根据开闭原则,在设计一个软件系统模块(类,方法)的时候,应该可以在不修改原有的模块(修改关闭)的基础上,能扩展其功能(扩展开放)。

扩展开放:某模块的功能是可扩展的,则该模块是扩展开放的。软件系统的功能上的可扩展性要求模块是扩展开放的。

修改关闭:某模块被其他模块调用,如果该模块的源代码不允许修改,则该模块修改关闭的。软件系统的功能上的稳定性,持续性要求模块是修改关闭的。

需要遵循开闭原则的原因

稳定性。开闭原则要求扩展功能不修改原来的代码,这可以让软件系统在变化中保持稳定。

扩展性。开闭原则要求对扩展开放,通过扩展提供新的或改变原有的功能,让软件系统具有灵活的可扩展性。

遵循开闭原则,可以提高代码的复用度,并且易于维护。

开闭原则的实现方法

为了满足开闭原则,应该对软件系统中的不变的部分加以抽象,在面向对象的设计中

1,可以把这些不变的部分加以抽象成不变的接口,这些不变的接口可以应对未来的扩展;

2,接口的最小功能设计原则。根据这个原则,原有的接口要么可以应对未来的扩展;不足的部分可以通过定义新的接口来实现;

3,模块之间的调用通过抽象接口进行,这样即使实现层发生变化,也无需修改调用方的代码。

简单地说,软件系统是否有良好的接口(抽象)设计是判断软件系统是否满足开闭原则的一种重要的判断基准。现在多把开闭原则等同于面向接口的软件设计。

里氏替换原则

里氏替换原则(Liskov Substitution Principle,LSP)要求所有引用基类的地方必须能透明地使用其派生类的对象。

只有满足以下 2 个条件的设计才可被认为是满足了 LSP 原则:

1,不应该在代码中出现 if/else 之类对派生类类型进行判断的条件。

2,派生类应当可以替换基类并出现在基类能够出现的任何地方,或者说如果我们把代码中使用基类的地方用它的派生类所代替,代码还能正常工作。

里氏替换原则(LSP)是使代码符合开闭原则的一个重要保证。

类的继承原则:如果一个派生类的对象可能会在基类出现的地方出现运行错误,则该派生类不应该从该基类继承,或者说,应该重新设计它们之间的关系。

动作正确性保证:从另一个侧面上保证了符合 LSP 设计原则的类的扩展不会给已有的系统引入新的错误。示例:里式替换原则为我们是否应该使用继承提供了判断的依据,不再是简单地根据两者之间是否有相同之处来说使用继承。

里式替换原则的优点

1,加强程序的健壮性,同时变更时也可以做到非常好地提高程序的维护性、扩展性。降低需求变更时引入的风险。

2,约束继承泛滥,是开闭原则的一种体现。

迪米特法则

迪米特法则又叫最少知道原则,迪米特法则(Law of Demeter ,LoD)要求一个软件实体应当尽可能少地与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

迪米特法则的优缺点

迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。

迪米特法则不希望类直接建立直接的接触。如果真的有需要建立联系,也希望能通过它的中间类来转达。

缺点:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系,这在一定程度上增加了系统的复杂度。

单一职责原则

单一职责原则(Single Responsibility Principle,SRP),如果一个类具有一个以上的职责,那么就会有多个不同的原因引起该类变化,而这种变化将影响到该类不同职责的使用者(不同用户);

1,单一职责原则可以使类的复杂度降低,实现什么职责都有清晰明确的定义;

2,类的可读性提高,复杂度降低,代码更容易维护;

3,变更引起的风险降低。

接口分隔原则

接口的设计原则(Interface Segregation Principle ,ISP):接口的设计应该遵循最小接口原则,不要把用户不使用的方法塞进同一个接口里。如果一个接口的方法没有被使用到,则说明该接口过胖,应该将其分割成几个功能专一的接口。

接口的依赖(继承)原则:如果一个接口 a 继承另一个接口 b,则接口 a 相当于继承了接口 b 的方法,那么继承了接口 b 后的接口 a 也应该遵循上述原则:不应该包含用户不使用的方法。反之,则说明接口 a 被 b 给污染了,应该重新设计它们的关系。

一个用户依赖了未使用但被其他用户使用的接口,当其他用户修改该接口时,依赖该接口的所有用户都将受到影响。这显然违反了开闭原则,也不是我们所期望的。

一个类对一个类的依赖应该建立在最小的接口上建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。

单一职责与接口隔离的区别:

1,单一职责原则注重的是职责,而接口隔离原则注重对接口依赖的隔离;

2,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。

依赖倒置原则

依赖倒置原则(Dependency Inversion Principle ,DIP)要求:

Ø 高层模块不应该依赖于低层模块,二者都应该依赖于抽象;

Ø 抽象不应该依赖于细节,细节应该依赖于抽象;

Ø 针对接口编程,不要针对实现编程。

依赖:在程序设计中,如果一个模块 a 使用/调用了另一个模块 b,我们称模块 a 依赖模块 b。

高层模块与低层模块:往往在一个应用程序中,我们有一些低层次的类,这些类实现了一些基本的或初级的操作,我们称之为低层模块;另外有一些高层次的类,这些类封装了某些复杂的逻辑,并且依赖于低层次的类,这些类我们称之为高层模块。

依赖倒置(Dependency Inversion):面向对象程序设计相对于面向过程(结构化)程序设计而言,依赖关系被倒置了。因为传统的结构化程序设计中,高层模块总是依赖于低层模块。

组合/聚合复用原则

组合/聚合复用原则(Composite/Aggregate Reuse Principle,CARP):尽量使用组合/聚合,不要使用类继承。

即在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,新对象通过向这些对象的委派达到复用已有功能的目的。就是说要尽量的使用合成和聚合,而不是继承关系达到复用的目的。

在继承结构中,父类的内部细节对于子类是可见的。所以通过继承的代码复用是一种白盒式代码复用。如果基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性;

组合是通过对现有的对象进行拼装(组合)产生新的、更复杂的功能。因为在对象之间,各自的内部细节是不可见的,所以我们也说这种方式的代码复用是黑盒式代码复用。(因为组合中一般都定义一个类型,所以在编译期根本不知道具体会调用哪个实现类的方法。)

相关推荐