写点什么

面向对象设计原则

用户头像
陈皮
关注
发布于: 2020 年 06 月 17 日
面向对象设计原则



高内聚是指相近的功能、行为应该放到同一个组件中。

设计原则

高內聚、低耦合

什么是“高内聚”

高内聚是指相近的功能、行为应该放到同一个组件中。

什么是“低耦合”

组件间的依赖关系清晰,交互不复杂。

面向对象编程、面向对象分析

面向对象编程不是使用面向对象的编程语言进行编程,而是利用多态特性进行编程,面向对象语言真正区别于其他高级语言的地方是多态

框架(frameworks)

框架是用来实现某一类应用的结构性程序,是对某一类架构方案可复用的设计与实现。

架构师通过开发或者维护框架来把控系统的质量。

面向对象设计的基本原则

OOD 原则一:开/闭原则(OCP)

OCP - Open/Closed Principle

  • 对扩展是开放的(Open for extension)

  • 对更改是封闭的(Closed for modification)

不需要修改现有软件实体,就能实现功能扩展

实现不修改而扩展的关键是抽象

DEMO

设计一个控制电话拨号的软件。

“拨打电话”的 Use Case描述:

  • 我们按下数字按钮,屏幕上显示号码,扬声器发出按键音

  • 我们按下Send按钮,系统接通无线网络,同时屏幕上显示正在拨号。

不太优雅的设计

类图





Demo Code
public class Button {
public final static int SEND_BUTTON = -99;
private Dialer dialer;
private int token;
public Button(int token, Dialer dialer) {
this.token = token;
this.dialer = dialer;
}
public void press() {
switch (token) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
dialer.enterDigit(token);
break;
case SEND_BUTTON:
dialer.dial();
break;
default:
throw new UnsupportedOperationException("unknown button pressed: token=" + token);
}
}
}
坏味道
  • 僵硬 - 不易增加、修改:增加一种Butn类型,就需要对 Button类进行修改;修改 Dialer,可能会影响 Button。

  • 脆弱- switch case/if elsei语句是相当脆弱的。当我想修改Send按钮的功能时,有可能不小心破坏数字按钮当这种函数很多时,我很有可能会漏掉某个函数,或其中的某个条件分支。

  • 不可移植-设想我们要设计密码锁的按钮,它只需要数字按键,但 Button的设计使它必须附带”send”类型的按钮。

改进 Button:方法一

抽象 Button 接口,分别实现 DigitButton 和 SendButton 实现类,分别调用 Dialer 业务方法。Dialer 类内部通过 if-else 判断决定,分别执行 digit/send。





改进 Button:策略模式

抽象 ButtonServer 接口,声明 ButtonServer#buttonPressed(int token) 方法,在 Dialer 中实现该方法,供 Button#process 调用。





Button 类伪代码

public class Button {
...
public void process(int token) {
buttonServer.buttonPressed(token);
}
...
}

改进 Button:适配器模式

定义两个 Adepter 类实现 ButtonServer 接口,分别调用 Dialer 类的 digit/send 方法。Dialer 和 ButtonServer 解耦,去除掉了 if-else 逻辑。





DigitButtonDailerAdepter 类伪代码:

public class DigitbuttonDilerAdepter implements ButtonServer {
...
@Override
public void buttonPressed(int token) {
dailer.enterDigit(token);
}
...
}

SendButtonDailerAdepter 类伪代码:

public class SendButtonDailerAdepter implements ButtonServer {
...
@Override
public void buttonPressed(int token) {
dailer.dail(token);
}
...
}

在此基础上新的需求:

同一 Button 同时需要触发多个功能。e.g. 点亮电话的灯。

可以通过增加新的 Adepter 来聚合不同的方法。

LampAndDigitButtonDailerAdepter 类伪代码:

public class LampAndDigitButtonDailerAdepter implements ButtonServer {
...
// enterDigit()
// lamp()
...
}

改进 Button:观察者模式

应对一个 button 按钮需要触发多个功能需求。

接口 ButtonServer 修改为 ButtonListener。

Button 类添加 List buttonListeners 字段,添加 addButtonListener(ButtonListener buttonListener) 方法。

Button#process 方法调整为迭代执行 buttonListeners 内所有对象。





phone 类组装

满足开闭原则,对功能扩展有较好的支持。

Phone 类伪代码:

public class Phone {
private Dialer dialer;
private Button[] digitButtons;
private Button sendButton;
public Phone() {
dialer = new Dialer();
digitButtons = new Button[10];
for (int i = 0; i < digitButtons.length; i++) {
digitButtons[i] = new Button();
final int digit = i;
digitButtons[i].addListener(new ButtonListener() {
public void buttonPressed() {
dialer.enterDigit(digit);
}
});
}
sendButton = new Button();
// 匿名函数定义 Adepter。
sendButton.addListener(new ButtonListener() {
public void buttonPressed() {
dialer.dial();
}
});
}
public static void main(String[] args) {
Phone phone = new Phone();
phone.digitButtons[9].press();
phone.digitButtons[1].press();
phone.digitButtons[1].press();
phone.sendButton.press();
}
}

OOD原则ニ:依赖倒置原则(DIP)

DIP - Dependency Inversion Principle

高层模块不依赖低层模块,而是依赖抽象。

抽象不依赖实现,而是实现依赖抽象。

DIP 倒置了什么?

  • 模块或者包的依赖关系

  • 开发顺序和职责(高层来定义、来调用)

软件的层次化

  • 高层决定低层

  • 高层被重用

e.g. Controller 层定义接口,自己调用, Service 层负责实现这个接口。





反例:





修改:





框架的核心

好莱坞原则:

  • Don't call me, I'll call you.

倒转的层次依赖关系。

应用不要调用框架,框架会来调用应用层实现。依赖是反过来的。

ooD原则三: Liskova替换原则(LSP)

子类必须能替换掉基类。

凡是使用基类的地方,一定也适用于其子类。

e.g. 人不能骑马,但是不能骑小马,违反原则。

子类抛出来的异常是父类抛出来的异常的子类,否则基类 catch 不到子类异常。

继承违反原则的时候使用组合(适配器模式)。





OOD原则四:单一职责原则(SRP)

SRP - Single Responsibility Principle

一个类只有一个引起它改变的原因。

单个类的职责少一些。

e.g.





改进





区分类的方法:分清职责

职责: 变化的原因

如果实现类不可拆分,采用接口隔离,分离职责。

OOD原则五:接口分离原则(ISP)

ISP - Interface Segregation Principle

不应该强迫客户程序依赖它们不需要的方法。

客户端看不到不需要的方法。

不暴露给客户端不需要的方法。

不要为了复用方法继承基类。

ISP和SRP的关系

  • ISP和SRP是相关的,都和“内聚性”有关。

  • SRP指出应该如何设计ー一个类ーー只能有一种原因才能促使类发生改变。

  • ISP指出应该如何设计一个接口ーー从客户的需要出发,强调不要让客户看到他们不需要的方法。

总结

OOD 原则

  • 开/闭原则(OCP)

  • 依赖倒置原则(DIP)

  • Liskova替换原则(LSP)

  • 单一职责原则(SRP)

  • 接口分离原则(ISP)



发布于: 2020 年 06 月 17 日阅读数: 56
用户头像

陈皮

关注

还未添加个人签名 2018.04.26 加入

还未添加个人简介

评论

发布
暂无评论
面向对象设计原则