架构学习总结 - 1 - 软件设计原则

用户头像
Chasedreamer
关注
发布于: 2020 年 06 月 14 日
架构学习总结 - 1 - 软件设计原则

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



面向对象编程不是使用面向对象的编程语言进行编程,而是利用多态特性进行编程

面向对象分析是将客观世界,级编程的业务领域进行对象分析

  • 充血模型与贫血模型

  • 领域驱动设计DDD



公司的业务本身就是按照对象来组织的,需要将业务领域里的对象抽象出来从而进行开发。针对客观世界的对象,及其相应的关系,分析并映射到编程语言中。



概念解释 - 多态

多态 - 就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性




框架

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

框架调用应用程序代码,应用程序调用工具

架构师用框架保证架构的落地,用工具提高开发效率



  • Junit是测试框架,Log4J是一个工具




软件设计的smelling



软件设计的最终目的,是使软件达到“强内聚,松耦合”,从而使软件

  • 易扩展 - 易于增加新的功能

  • 更强壮 - 不容易被粗心的程序员破坏

  • 可移植 - 能够在多样的环境下运行

  • 更简单 - 容易理解,容易维护

与之相反,一个“不好的”软件,会发出如下“臭味”:

  • 僵硬 - 不好更改

  • 脆弱 - 只想改变A,结果B被意外破坏

  • 不可移植 - 不能适应环境变化

  • 导致误用的陷阱 - 做错误的事情比正确的事请更容易,引诱程序员破坏原有的设计

  • 晦涩 - 代码难以理解

  • 过度设计, copy-paste代码



僵硬性 - 很难对系统进行改动,因为每个改动都会迫使许多对系统其他部分的改动

  • 如果单一的改动会导致依赖关系的模块中的连锁改动,那么设计就是僵化的,必须要改动的模块越多,设计就越僵化

牵一发而动全身



脆弱性 - 对系统的改动会导致系统中和改动无关的许多地方出现问题

  • 出现新问题的地方与改动的地方没有概念上的关联。要修正这些问题又会引出更多的问题。

改了一个地方出现了更多的问题



牢固性 - 很难解开系统的纠结,使之成为一些在其他系统之可重用的组件

  • 设计中包含了对其他系统有用的部分,而把这些部分从系统中分离出来所需要的努力和风险是巨大的

没法抽离重用



粘滞性 - 做正确的事情比做错误的事请要困难

  • 面临一个改动的时候,开发人员常常会发现会有许多改动的方法。有的方法会保持系统原来的设计,而另一些方法会破坏设计。当可以保持系统设计的方法比那些破坏设计额的方法更难应用时,就表明设计具有更高的粘滞性,做错误的事情就很容易

只能照着现有的方式写,没法重构



不必要的复杂性 - 设计中包含不具有任何直接好处的基础结构

  • 如果设计中包含当前没有用的组成部分,他就含有不必要的复杂性,当开发人员预测需求的变化,并在软件中放置了处理那些潜在变化的代码时,常常会出现这种情况。

写了太多的没用的基础代码



不必要的重复 - 设计中包含有重复的结构,而该重复的结构本可以使用单一的抽象进行统一

  • copy-paste编程



晦涩性 - 很难阅读理解




面向对象原则以及实例练习



实例以及基本设计





首先识别对象 - 按钮,拨号器,扬声器,话筒,屏幕



可以画出以下类图



以及协作图



根据UML可以想象出以下代码

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);
}
}
}
public class Dialer {
public void enterDigit(int digit) {
screen.display(digit);
speaker.beep(digit);
}
public void dial() {
screen.display("dialing...");
radio.connect();
}
}



基本设计的问题



僵硬 - 不易修改,增加

  • 增加一种button类型,就需要对button类进行修改

  • 修改Dialer可能会影响button



脆弱 - Switch case \ If-else 语句是相当脆弱的

  • 当我想修改Send按钮的功能时,可能不小心破坏数字按钮

  • 当这种函数很多时,我很有可能会漏掉某个函数,或者其中某个条件的分支



不可移植

  • 设想我们要设计密码锁的按钮,它只需要数字按键,但button的设计使他必须附带一个send类型的按钮



OOD原则一:开闭原则 OCP

OCP - Open/Closed Principle

  • 对于扩展是开放的 Open for extension

  • 对于更改是封闭的 Closed for modification

  • 不需要修改软件实体就应该能时现功能的扩展

  • 关键是抽象





public interface IButton {
void press();
}
public class DigitButton implements IButton{
private Dialer dialer;
private int token;
public DigitButton(int token, Dialer dialer) {
this.token = token;
this.dialer = dialer;
}
public void press() {
dialer.enterDigit(token);
}
}
public class SendButton implements IButton {
private Dialer dialer;
public SendButton(Dialer dialer) {
this.dialer = dialer;
}
public void press() {
dialer.dial();
}
}
public class Dialer {
public void enterDigit(int digit) {
screen.display(digit);
speaker.beep(digit);
}
public void dial() {
screen.display("dialing...");
radio.connect();
}
}





public interface IButtonServer {
void buttonPressed(int token);
}
public class Button {
private IButtonServer _buttonServer;
private int _token;
public Button(int token, IButtonServer buttonServer) {
_token = token;
_buttonServer = buttonServer;
}
public void press() {
buttonServer.buttonPressed(token);
}
}
public class Dialer implements IButtonServer {
public void buttonPressed(int token) {
switch (token) {
case 0: case 1: case 2: case 3: case 4:
case 5: case 6: case 7: case 8: case 9:
enterDigit(token);
break;
case SEND_BUTTON:
dial();
break;
default:
throw new UnsupportedOperationException("unknown button pressed: token=" + token);
}
}
}
public void enterDigit(int digit) {
screen.display(digit);
speaker.beep(digit);
}
public void dial() {
screen.display("dialing...");
radio.connect();
}
}





public interface IButtonServer {
void buttonPressed(int token);
}
public class Button {
private IButtonServer _buttonServer;
private int _token;
public Button(int token, IButtonServer buttonServer) {
_token = token;
_buttonServer = buttonServer;
}
public void press() {
buttonServer.buttonPressed(token);
}
}
public class Dialer {
public void enterDigit(int digit) {
screen.display(digit);
speaker.beep(digit);
}
public void dial() {
screen.display("dialing...");
radio.connect();
}
}
public class DigitButtonDailerAdapter implements IButtonServer {
private Dailer _dailer;
public DigitButtonDailerAdapter(Dailer dailer){
_dailer = dailer;
}
public void buttonPressed(int token) {
_dailer.enterDigit(token);
}
}
public class SendButtonDailerAdapter implements IButtonServer {
private Dailer _dailer;
public DigitButtonDailerAdapter(Dailer dailer){
_dailer = dailer;
}
public void buttonPressed(int token) {
_dailer.dial();
}
}





public interface ButtonListener {
void buttonPressed();
}
public class Button {
private List<ButtonListener> listeners;
public Button() {
this.listeners = new LinkedList<ButtonListener>();
}
public void addListener(ButtonListener listener) {
listeners.add(listener);
}
public void press() {
for (ButtonListener listener : listeners) {
listener.buttonPressed();
}
}
}
public class Dialer {
public void enterDigit(int digit) {
screen.display(digit);
speaker.beep(digit);
}
public void dial() {
screen.display("dialing...");
radio.connect();
}
}
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();
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倒置了

  • 模块或包的依赖关系

  • 开发的顺序和职责





Controller是高层模块,Service是低层模块

高层定义接口,低层实现接口

OOD原则三:Liskov替换原则 LSP

时现OOP的关键在于抽象,抽象的核心是多态和继承

正确的继承必须保证 - 子类型 subtype 必须能够替换掉它的基类型 base type



不应该用if-else,应该使用不同的图形类继承drawShape方法







LSP要求,凡是使用基类的地方,一定也适用于子类

  • 子类一定拥有基类的整个接口

  • 子类的访问控制不能比基类更严格

  • 例如Object类中有一个方法 protected Object clone(), 子类可以 override 并放松其控制 public Object clone(), 但反过来不能将public的访问控制缩小成private,编译器不会通过



解决LSP问题的方法

提取共性到基类
改成组合

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

一个类,只能有一个引起他变化的原因



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

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



用户头像

Chasedreamer

关注

还未添加个人签名 2018.10.25 加入

还未添加个人简介

评论

发布
暂无评论
架构学习总结 - 1 - 软件设计原则