软件设计与体系结构

ObjectKaz Lv4

概述

UML

统一建模语言,通过它构造系统结构的蓝图。

用例图

对应用户视图,表示多个外部执行者与系统用例之间,以及用例之间的关系。

类图

对应结构视图,使用类描述系统的静态结构,并包含类和他们之间的关系。

绘制元素

  1. 类:类名、属性、方法

  2. 属性写法: 可见性 名称:类型 [ = 默认值]

  3. 方法写法: 可见性 名称(参数列表) [:返回值类型]

  4. 可见性:

    • +: public
    • #: protected
    • -: private

类与类之间的关系

面向对象设计原则

原则解释
单一职责原则一个类只做一件事情。
开闭原则一个类对扩展开放,对修改关闭。实现方法是抽象化。
里氏代换原则接受基类的地方,可以用子类替换。
依赖倒置原则一个类应该依赖于接口而不依赖于具体实现;细节依赖抽象,抽象不依赖细节。
接口隔离原则一个接口如果太大,则应该使用多个专门的接口来取代。但接口的粒度要合适。
合成复用原则多使用组合和聚合,尽量少用继承。
迪米特法则(最少知识原则)一个软件实体对其他实体的引用越少越好。

依赖注入

程序运行过程中,如果需要调用另一个对象时,无须在代码中创建被调用者,而是依赖于外部的注入。

  1. 构造注入:在构造函数中注入实例变量
  2. 设值注入:通过setXxxx 方法注入实例变量
  3. 接口注入:通过接口定义的方法,在接口实现中注入实例变量

Java EE

C/S 和 B/S 架构

C/S 架构(客户端-服务器)

优点:

  1. 安全性好:C/S 程序部署在特定的客户端,系统的操作用户通常比较确定
  2. 效率高:客户端和服务器直接相连,数据传输比较快,系统运行效率比较高
  3. 个性化:可以根据需要对不同客户端上的程序进行界面和功能方面的定制,满足客户的个性化要求
  4. 稳定性强:C/S 结构比较稳定,有较强的事务处理能力,可以实现较复杂的业务逻辑

缺点:

  1. 适用面窄:C/S 程序通常部署于局域网中,能够使用的业务场景较少,适用范围较窄
  2. 用户群固定:C/S 架构系统需要安装客户端程序才可以使用,不适合面向不可知的用户
  3. 维护成本高:C/S 程序升级时需要对所有客户端程序进行升级,维护成本较高

B/S 架构(浏览器-服务器)

优点:

  1. 客户端免安装
  2. 交互性强
  3. 维护成本低

缺点:

  1. 浏览器兼容性差
  2. 效率低
  3. 安全风险高
  4. 实时性差

三层架构

创建型设计模式

工厂:对象的创建和使用分离

简单工厂模式

动机

用户希望通过不同的参数得到不同的对象,而不需要知道具体的创建过程。

角色

  1. 工厂:用来产生类
  2. 抽象接口:这些产生的类需要继承一个父类
  3. 具体类:需要被产生的类

关系:

  1. 具体类和抽象接口具有 接口实现关系
  2. 具体类和工厂具有 依赖关系

改进:工厂和抽象类合二为一

类图

核心代码

1
2
3
4
5
6
7
8
9
10
11
public class SimpleFactory {
public static Product createProduct(String owner) {
if (owner.equals("A")) {
return new ConcreteProductA();
} else if (owner.equals("B")) {
return new ConcreteProductB();
} else {
return null;
}
}
}

优点

  1. 创建和使用分离
  2. 客户无需知道需要具体创建的类,只需要提供参数
  3. 通过配置类可以在不修改代码的同时增加新的类

缺点

  1. 工厂类集中的所有的产品创建逻辑
  2. 增加了系统中类的个数
  3. 当产品类型过多时,工厂的逻辑会比较复杂
  4. 简单工厂使用了静态的工厂方法,无法被继承
  5. 增加产品时需要修改工厂类,违反开闭原则

使用场景

  1. 工厂类需要创建的对象很少
  2. 客户端只需要知道工厂类的参数

工厂方法模式

动机

简单工厂模式中,当产品类型过多时,工厂的逻辑会比较复杂。当需要增加新的产品类型时,需要修改工厂类,违反了开闭原则。

角色

  1. 抽象工厂:定义了工厂方法
  2. 具体工厂:返回一个具体类的实例
  3. 抽象产品:这些被产生的类需要继承一个父产品
  4. 具体产品:需要被产生的产品

关系:

  1. 抽象工厂和具体工厂是接口实现关系
  2. 抽象产品和具体产品是接口实现关系
  3. 具体工厂和具体产品是依赖关系

类图

核心代码

1
2
3
4
5
6
7
8
9
10
11
//抽象工厂
public interface Factory {
Product createProduct();
}

// 具体工厂
public class ConcreteFactory extends Factory {
public Product createProduct() {
return new ConcreteProduct();
}
}

优点

  1. 工厂可以自主确定创建什么对象
  2. 客户只需关心所需产品对应的工厂,不需要关心细节
  3. 增加新的产品时,只需要增加一个具体工厂,不需要修改原有工厂类

缺点

  1. 增加新产品时,需要编写具体的产品类,又要编写工厂类。
  2. 增加了系统的抽象层次和理解难度。

使用场景

  1. 客户端不知道具体产品类,只知道对应类的工厂类
  2. 一个类通过子类来确定创建什么对象
  3. 将创建对象的任务委托给众多工厂子类中的一个,此时可以通过配置文件来确定

抽象工厂模式

动机

一个工厂需要提供多个产品对象

概念

  1. 产品等级结构:产品的继承结构
  2. 产品族:一个工厂生产的不同产品

角色

  1. 抽象工厂:定义了工厂方法
  2. 具体工厂:返回一个具体类的实例
  3. 抽象产品:这些被产生的类需要继承一个父产品
  4. 具体产品:需要被产生的产品

关系:

  1. 抽象工厂和具体工厂是接口实现关系
  2. 抽象产品和具体产品是接口实现关系
  3. 具体工厂和具体产品是依赖关系

一个具体工厂对应了多个具体产品。

类图

假设有产品族有WindowsLinuxMac 三种,有ButtonLabel两种产品等级结构:

优点

  1. 隔离了具体类的生成,客户不知道具体类被创建
  2. 当一个产品族的多个对象被设计在一起工作时,它能够保证客户端始终只使用同一个产品族的对象
  3. 容易增加新的工厂和产品族

缺点

  1. 难以增加新的产品

适用场景

  1. 系统存在多个产品族,每次只使用一个产品族
  2. 仅同一个产品族的产品在一起使用

单例模式

动机

一个类只有一个实例

类图

核心代码

注意将构造函数设置成私有的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 饿汉式单例模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}

// 懒汉式单例模式
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

优点

  1. 提供了对唯一对象的受控访问
  2. 节省系统资源

缺点

  1. 难以扩展
  2. 单例类的职责过重
  3. 可能带来一些负面问题

适用场景

  1. 一个类只需要一个对象

结构型设计模式

适配器模式

动机

将一个接口转换成用户期望的接口,使得这些不兼容的类能够一起工作。

角色

  1. 目标抽象类 Target:针对用户提供的接口
  2. 适配器类 Adapter: 将不兼容的接口转换成兼容的接口
  3. 适配者类 Adaptee: 原先不兼容的接口和类

两种适配模式

  1. 对象适配器:通过关联Adaptee对象的方式进行适配
  2. 类适配器:通过继承Adaptee和Target的方式进行适配。若不支持多继承,则通过接口实现的方式来完成。

类图

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 对象适配器
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
}

// 类适配器
public class Adapter extends Adaptee implements Target {
public void request() {
super.specificRequest();
}
}

优点

  1. Target和Adaptee解耦
  2. 增加了类的透明性和复用性
  3. 灵活性和可扩展性好
  4. 对象适配器支持适配Adaptee及其子类

缺点

  1. 类适配器在不支持多重继承的语言上有限制
  2. 对象适配器难以置换Adaptee的方法。

适用场景

  1. 系统中需要使用现有的类,但接口设计不满足需求
  2. 建立一个可以重复使用的类,用于一些没有太大关联的类

拓展

  1. 缺省适配器:有时候不需要实现某个接口提供的全部方法,子类可以有选择的覆盖父类的方法。可以先设计一个父类,为这些方法提供一个默认操作,在通过子类继承的方式有选择的覆盖这些方法。
  2. 双向适配器:适配器同时包含了对Target和Adaptee的引用,Adaptee也可以通过Adapter调用Target的方法。

装饰器模式

动机

通过对用户透明的方式,动态地为对象增加一些新的功能。

角色

  1. 抽象组件 Component
  2. 具体组件 ConcreteComponent
  3. 抽象装饰器 Decorator
  4. 具体装饰器 ConcreteDecorator

关系:

  1. 具体组件和抽象组件是继承关系
  2. 具体装饰器和抽象装饰器是继承关系
  3. 抽象装饰器和抽象组件是继承关系

类图

核心代码

1
2
3
4
5
6
7
8
9
10
// 抽象装饰器
public abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
component.operation();
}
}

优点

  1. 装饰模式非常灵活,可以动态扩展
  2. 可以实现不同组合的装饰器,从而为对象增加不同的功能。

缺点

  1. 产生很多小对象和具体装饰类
  2. 更容易出错

适用场景

  1. 动态增加类的功能,增加对象职责
  2. 不能通过继承的方式来扩充系统

代理模式

动机

给一个对象提供一个代理,并通过代理对象控制对原对象的访问。

角色

  1. 抽象主题 Subject,被代理主题和代理的抽象接口
  2. 具体主题 RealSubject,被代理的主题
  3. 代理 Proxy,控制对RealSubject的访问

类图

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 代理
public class Proxy implements Subject {
private RealSubject realSubject;
public Proxy() {
realSubject = new RealSubject();
}
public void request() {
beforeRequest();
realSubject.request();
afterRequest();
}
private void beforeRequest() {
System.out.println("Proxy before request");
}
private void afterRequest() {
System.out.println("Proxy after request");
}
}

优点

  1. 协调调用者和被调用者
  2. 远程代理:客户端可以访问远程对象
  3. 虚拟代理:使用小对象代表大对象,减小系统资源消耗
  4. 保护代理:控制真实对象的使用权限

缺点

  1. 可能会降低请求速度
  2. 有些额外的工作的实现比较··复杂

适用场景

  1. 远程代理:为不同地址空间的对象提供一个本地的代理
  2. 虚拟代理:小对象代表大对象
  3. 保护代理:控制对象的访问
  4. 缓冲代理:为操作结果提供临时的存储空间,以便多个客户端可以共享这些结果
  5. 防火墙代理:保护目标不让恶意用户接近
  6. 同步化代理:使用户能够同时使用某个对象,且不出现冲突
  7. 智能引用代理:当一个对象被引用时,提供一些额外的操作

行为型设计模式

命令模式

动机

将一个请求封装为一个对象,从而使得我们可用不同的请求对客户进行参数化;对请求排队或者记录日志,以及支持可撤销的操作。

角色

  1. 抽象命令 Command:所有命令的抽象
  2. 具体命令 ConcreteCommand:实现抽象命令
  3. 调用者 Invoker:设置并调用命令
  4. 接收者 Receiver:执行与请求有关的操作

类图

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 命令
public abstract class Command {
protected Receiver receiver;
public Command(Receiver receiver) {
this.receiver = receiver;
}
public abstract void execute();
}
// 具体命令
public class ConcreteCommand extends Command {
public ConcreteCommand(Receiver receiver) {
super(receiver);
}
public void execute() {
receiver.action();
}
}
// 接收者
public class Receiver {
public void action() {
System.out.println("Receiver action");
}
}

// 调用者
public class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void action() {
command.execute();
}
}

优点

  1. 降低系统的耦合度
  2. 新的命令很容易加入系统中
  3. 容易设计命令队列和宏命令
  4. 可以方便的实现Undo和Redo

缺点

  1. 系统会出现很多的具体命令类

适用场景

  1. 请求的调用者和接收者解耦
  2. 请求排队执行
  3. 需要支持撤销和重做
  4. 系统需要将一组操作组合在一起

观察者模式

动机

对象间的一对多依赖关系,使得一个对象状态改变时,其相关依赖能得到通知并被自动更新。

角色

  1. 目标 Subject:被观察的对象,它的状态变化就需要通知观察者
  2. 具体目标 ConcreteSubject:实现Subject接口,实现相关操作
  3. 观察者 Observer:观察者需要关注的对象,它需要接收以及处理目标对象的通知
  4. 具体观察者 ConcreteObserver:实现Observer接口,实现相关操作

类图

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 观察者
public interface Observer {
public void update(String message);
}
// 具体观察者
public class ConcreteObserver implements Observer {
public ConcreteObserver() {
}
public void update(String message) {

}
}
// 目标
public interface Subject {
public void attach(Observer observer);
public void detach(Observer observer);
public void notify(String message);
}
// 具体目标
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<Observer>();
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notify(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}

优点

  1. 表示层和数据逻辑层的分离
  2. 在目标和观察者之间建立了一个抽象的耦合
  3. 支持广播
  4. 符合开闭原则

缺点

  1. 如果目标对象有很多的观察者,那么通知观察者会耗费较多时间。
  2. 如果存在循环依赖,可能会崩溃
  3. 只能观察到目标发生了变化,但不指定具体的变化

适用场景

  1. 一个对象需要通知其他对象,但不知道具体的对象
  2. 需要在系统中创建触发链
  3. 一个对象改变导致很多其他对象发生改变
  4. 一个抽象模型有两个方面,其中一个方面依赖于另一个方面
  • 标题: 软件设计与体系结构
  • 作者: ObjectKaz
  • 创建于: 2022-06-05 03:16:29
  • 更新于: 2022-06-05 13:04:30
  • 链接: https://www.objectkaz.cn/93709eeba98f.html
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。