面向对象的三大特性和五大设计原则

it2022-05-09  40

特性 封装继承多态SOLID 设计原则 单一职责原则开放封闭原则里式替换原则接口隔离原则依赖倒置原则 台灯和按钮的例子:替换数据库其他设计原则 合成复用原则迪米特法则

面向对象的 SOLID 原则

特性

封装

隐藏对象的属性和实现细节,通过对外暴露的接口控制程序中属性的读写。

封装需要把所有属性私有化,对每个属性提供 getter 和 setter 方法。如果有一个带参的构造函数的话,还需要写一个不带参的构造函数。

继承

通过继承可以实现代码的复用。

缺点是提高了类之间的耦合性。

多态

基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。

要想实现多态,需要在继承+覆盖或重载。

覆盖,是指子类重新定义父类的虚函数的做法。重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

SOLID 设计原则

面向对象的 SOLID 设计原则:

S-单一职责原则O-开放封闭原则L-里氏替换原则I-接口隔离原则(Interface segregation principles,ISP)D-依赖倒置原则(Dependency inversion principle,DIP)

单一职责原则

一个类只做一件事。

比如订单和账单,都包括订单号、创建时间等信息,但是如果只用一个类来表达,会导致:

特有属性和共有属性相互掺杂,难以理解修改一个场景可能会影响另一个场景

开放封闭原则

实体应该对扩展是开放的,对修改是封闭的。即可扩展而不可修改。哪里可能变化,就在哪里抽象。

例如,对于支付场景,如果将多种支付方式整合到一个类中,每次添加一种新的支付方式都需要变更这个类,并可能导致已有的支付方式受到影响而无法使用。可以将其抽象为抽象类或接口,每种支付方式继承抽象类或实现接口。

interface PayProcessor { Result<T> handle(Param param); } public class AlipayProcessor implements PayProcessor { ... } public class WeChatPayProcessor implements PayProcessor { ... }

里式替换原则

在对象出现的任何地方,都可以用其子类实例进行替换,而不会导致程序错误,且软件功能不受影响。

对于继承关系,只有满足里式替换原则才算合理。里氏替换原则的关键在于不能覆盖父类的非抽象方法。父类实现好的方法设定了一系列规范和契约,虽然它不强制要求所有的子类必须遵从这些规范,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。

例如,B 在继承 A 时,修改了其非抽象方法:

class A { public int func1(int a, int b) { return a-b; } } class B extends A { public int func1(int a, int b) { return a+b; } public int func2(int a, int b) { return func1(a,b)+100; } } public class Client { public static void main(String[] args) { A a = new A(); System.out.println("100-50="+a.func1(100, 50)); B b = new B(); System.out.println("100-50="+b.func1(100, 50)); System.out.println("100+20+100="+b.func2(100, 20)); } }

接口隔离原则

客户不应被强迫依赖它不使用的方法。一个类实现的接口中,包含了它不需要的方法。将接口拆分成更小和更具体的接口,有助于解耦,从而更容易重构、更改。

例如,假设对于支付接口,支付宝支持收费和退费,微信只支持收费。如果只抽象一个支付接口,则必须包括收费和退费两个方法:

interface PayChannel { void charge(); void refund(); } class AlipayChannel implements PayChannel { public void charge() { ... } public void refund() { ... } } class WeChatChannel implements payChannel { public void charge() { ... } public void refund() { // 没有任何代码 } }

此时微信不得不将退费实现成空方法。

要满足接口隔离原则,可以将支付接口拆成各包含一个方法的收费和退费两个接口。支付宝需要同时实现这两个接口,而微信只需要实现收费接口。

依赖倒置原则

高层次的模块不应依赖低层次的模块,他们应该都依赖于抽象。抽象不应依赖于具体实现,具体实现应依赖抽象。

可以用于将业务和底层模块解耦(例如底层逻辑经常变化,或预计会在一段时间后替换底层模块)。

台灯和按钮的例子:

Button 对象用于感知外部环境。Lamp 对象用于影响外部环境。

下图直接使用继承。此时 Lamp 对象的任何修改都会影响到 Button 对象。这个模型可以通过反转 Lamp 对象的依赖性来改进。

public class Button { private Lamp lamp; public void Poll() { if (/*some condition*/) { lamp.TurnOn(); } } }

下图使用了依赖倒置原则。Button 对象现在拥有一个与 ButtonServer 相关的关联,它提供了 Button 可以用来打开或关闭某些东西的接口。Lamp 实现 ButtonServer 接口。此时,Button 对象可以控制所有实现了 ButtonServer 接口的对象,更灵活,同时还可以控制目前尚未创建的对象。

替换数据库

假设目前使用 MySQL,但是后面可能换成其它数据库。针对 MySQL 的实现类 MySQLDBUtil 编程的话,后续假设又创建了 OracleDBUtil,则替换的工作量相当大。而如果加一个抽象层接口 DBUtil,所有的数据库类(MySQLDBUtil、OracleDBUtil)都实现 MySQLDBUtil 接口,就可以轻松替换。

原设计,直接继承数据库操作类:

新设计,增加抽象层接口。注意,这里用关联关系代替了继承关系,具体参考下面的合成复用原则:

其他设计原则

合成复用原则

合成复用原则(Composite Reuse Principle, CRP):尽量使用对象组合和聚合关系,而不是继承。组合和聚合关系可以降低类之间的耦合度。如果使用继承,需要严格遵循里氏替换原则。

数据库工具类:

class DBUtil { }

继承关系:

class UserDAO extends DBUtil { ... }

聚合关系:

class UserDAO { private DBUtil util; ... }

迪米特法则

迪米特法则又叫最少知道原则:一个对象应该对其他对象保持最少的了解。

迪米特法则特点:

类之间解耦,降低耦合度会产生大量的中转类,导致系统的复杂度提高。

举个例子:连长、班长、士兵之间布置任务,连长只需要传达给其直接下属班长,班长再传达给其负责的士兵(下图左侧示例)。如果违反了迪米特法则,将导致混乱及高耦合度(下图右侧示例)。

转载于:https://www.cnblogs.com/kika/p/10851581.html

相关资源:面向对象的三大特性五项原则

最新回复(0)