软件架构 (2)- 框架设计

用户头像
Zeke
关注
发布于: 2020 年 09 月 30 日
软件架构(2)-框架设计

1、面向对象编程

1.1、什么是面向对象?

Booch对于对象的描述:对象具有状态、行为和标识。

  • 状态:标明每个对象可以有自己的数据。(成员变量)

  • 行为:表明每个对象可以产生行为。(方法或者函数)

  • 标识:表明每个对象都区别于其他对象。

 

一个不能进行交互,没有行为的东西,是不能成为对象的,对象应该是一个客观存在。

 

1.2、面向对象编程三要素(特征)

封装性(Encapsulation)

  • 隐藏实现细节(访问控制)

  • 定义接口

继承性(Inheritance)

  • IS-A关系

  • Has-A关系(组合)

多态性

  • 后期绑定(虚函数)

  • 向上转形(Up casting)

 

3、OOD原则

3.1、开/闭原则(OCP)

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

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

  • 简言之:不需要修改软件实体(类、模块、函数等),就能实现功能的拓展。

 

如何实现不修改而拓展?关键是抽象

 

3.2、依赖倒置原则(DIP)

  • 高层模块不能依赖低层模块,而是大家都依赖于抽象;

  • 抽象不能依赖实现,而是实现依赖于抽象。

 

所谓倒置,倒置了模块或包的依赖关系倒置了开发顺序和职责

 

高层定义接口,高层依赖这个接口,低层参考这个接口实现低层逻辑。

 

框架的核心--好莱坞原则:

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

倒转层次的依赖关系

 

3.3、里氏替换原则(LSP)

主要用来解决继承的问题。

在Java/C++这样的静态类型语言中,实现OCP的关键在于抽象,而抽象的威力在于多态和继承。一个正确的继承应该要符合里氏替换原则

 

关于里氏替换原则的简述:

对于程序使用基类型(base type)的场景,能够使用子类型(subtype)替换掉这个基类型,使得程序的行为功能保持不变。这个子类型对基类型的继承就是合理的,符合里氏替换原则的

 

反之这个子类对父类的继承就是不合理的。并不是is-a关系,就表示是合理的继承,还要看程序使用的场景。

 

里氏替换原则:

1、判断继承是否合理。

2、必须依托实际场景来分析继承是否合理。

3、高度抽象相同行为操作,父类不关心子类怎么实现,父类提供对外统一的行为操作。

4、里氏替换原则通常会组合策略模式达到项目功能的可扩展性与可维护性。

 

继承 VS 组合

两者都是OOP的拓展手段。

继承优点:

  • 比较容易,因为基类的大部分功能可以通过继承直接进入子类。

继承缺点:

  • 继承破坏了封装,因为继承将基类更多的细节暴露给子类。因而继承被称为"白盒复用"。

  • 当基类发生改变时,可能会层层影响其下的子类。

  • 继承是静态的,无法在运行时改变组合。

  • 类数量爆炸。

 

应优先使用组合

 

可能违反LSP的征兆例子:

派生类中的退化函数

public class Base {
public void func() {
//do something.
}
}
public class Derived extends Base {
public void func() {}
}

 

派生类中抛出基类不会产生的异常

public class Derived extends Base {
public void func() {
throw new UnsupportedOperationException();
}
}

 

3.4、单一职责原则(SRP)

类应该是内聚的,内聚就是相同的功能要聚合在一起,相同的职责要聚合在一起。当一个类,引起它变化的原因有很多,那它的职责就不是单一的。一个类,应只能有一个引起它变化的原因

 

例一

  • Rectangle类包含了两个职责:

  • draw()在GUI上画出自己。

  • area()用来计算自身面积。

  • 有两个应用分别依赖Rectangle:

  • 计算几何应用,利用Rectangle计算面积

  • 图形应用,利用Rectangle绘制长方形,也需要计算面积。

 



缺点

  • 脆弱性 - 绘图和计算功能耦合在一起,当修改其中一个,另一个功能可能会以外受损。

  • 不可移植性 - 计算几何应用只需要使用area()的功能,却不得不包含GUI的依赖

 

改进





将几何计算功能area()移植到Geometric RectangleRectangle依赖Geometric Rectangle完成几何的计算。

 

区分类方法的职责

有时候,一个类包含的职责并不明显

interface Modem {
void dial(String pno); // 拨号
void hangup(); // 挂断
void send(char c); // 发送
void recv(); // 接收
}

当应用程序连接Modem的方式发生变化,如dial参数发生改变,此时会有”僵化性“问题。因此,”连接“和”收发“两个职责应当分开。

 

3.5、接口分离原则(ISP)

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

 

SRPISP是相关的,都和”内聚性“有关,但侧重点不同。

 

以前面Modem为例

连接“环节和”收发数据“环节有内在关系,必须写在一个类中。但是仍可以从接口上将二者分开,当”连接“发生变化时,只关注”收发数据“的程序不会受影响。





胖接口例子

一个可定时开关的门的类设计

interface Door extends TimerClient {
void lock();
void unlock();
boolean isDoorOpen();
}
class Timer {
void register(int timeout, TimerClient client) {
// do something
};
}
interface TimerClient {
void timeout();

Door类的接口包含了timeout方法,但是这个方法对不需要timeout机制的门是没有用的。当TimerClient的timeout()方法发生改变时,所有不需要timeout机制的门也被迫改变。



改进

方法1. 适配器



方法2. 多继承



4、反应式框架Flower的设计

Flower要解决的问题:

在高并发的编程环境下,线程的阻塞导致的资源占用,最终导致系统崩溃问题。

 

解决办法:

使用异步,线程不会互相等待,不会互相阻塞。基于Akka框架的Actor模型进行封装而成。



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

Zeke

关注

还未添加个人签名 2018.05.03 加入

还未添加个人简介

评论

发布
暂无评论
软件架构(2)-框架设计