面向对象设计原则

高内聚是指相近的功能、行为应该放到同一个组件中。
高内聚是指相近的功能、行为应该放到同一个组件中。
组件间的依赖关系清晰,交互不复杂。
面向对象编程不是使用面向对象的编程语言进行编程,而是利用多态特性进行编程,面向对象语言真正区别于其他高级语言的地方是多态。
框架是用来实现某一类应用的结构性程序,是对某一类架构方案可复用的设计与实现。
架构师通过开发或者维护框架来把控系统的质量。
OCP - Open/Closed Principle
不需要修改现有软件实体,就能实现功能扩展。
实现不修改而扩展的关键是
抽象。
设计一个控制电话拨号的软件。
“拨打电话”的 Use Case描述:

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);
}
}
}
僵硬 - 不易增加、修改:
脆弱- switch case/if elsei语句是相当脆弱的。
抽象 Button 接口,分别实现 DigitButton 和 SendButton 实现类,分别调用 Dialer 业务方法。Dialer 类内部通过 if-else 判断决定,分别执行 digit/send。

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

Button 类伪代码
public class Button {
...
public void process(int token) {
buttonServer.buttonPressed(token);
}
...
}
定义两个 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 按钮需要触发多个功能需求。
接口 ButtonServer 修改为 ButtonListener。
Button 类添加 List
Button#process 方法调整为迭代执行 buttonListeners 内所有对象。

满足开闭原则,对功能扩展有较好的支持。
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();
}
}
DIP - Dependency Inversion Principle
高层模块不依赖低层模块,而是依赖抽象。
抽象不依赖实现,而是实现依赖抽象。
DIP 倒置了什么?
软件的层次化
高层被重用e.g. Controller 层定义接口,自己调用, Service 层负责实现这个接口。

反例:

修改:

好莱坞原则:
倒转的层次依赖关系。
应用不要调用框架,框架会来调用应用层实现。依赖是反过来的。
子类必须能替换掉基类。
凡是使用基类的地方,一定也适用于其子类。
e.g. 人不能骑马,但是不能骑小马,违反原则。
子类抛出来的异常是父类抛出来的异常的子类,否则基类 catch 不到子类异常。
继承违反原则的时候使用组合(适配器模式)。

如果设计目的不是为了其他类继承的类,最好不要去继承它。
子类不应该比基类语义上更严格。
SRP - Single Responsibility Principle
一个类只有一个引起它改变的原因。
单个类的职责少一些。
e.g.

改进

职责: 变化的原因
如果实现类不可拆分,采用接口隔离,分离职责。
ISP - Interface Segregation Principle
不应该强迫客户程序依赖它们不需要的方法。
客户端看不到不需要的方法。
不暴露给客户端不需要的方法。
不要为了复用方法继承基类。
ISP和SRP的关系
OOD 原则