【期末速成】软件设计模式与体系结构
一、单选题:1分*10,共10分
二、填空题:1分*10,共10分
三、设计重构题:10分
四、分析题:20分
五、综合分析设计题:50分
面向对象设计原则
- 单一职责原则
- 又称单一功能原则。有且只有一个原因引起类的变更。如果多个,应该拆分。
- 所谓职责是指类变化的原因
- 开闭(Open-Close)原则
- 软件中的实体(包括类,模块,函数等等),应当是可扩展的(开),而不应被修改(闭)
- 即:应该对于扩展是开放的,但是对于修改是封闭的
- 是指软件实体应尽量在不修改原有代码的情况下进行扩展
- 里氏替换(里氏)原则
- 父类的方法都要在子类中实现或者重写。
- 不允许子类出现父类所没有定义的方法。
- 继承必须确保超类的所拥有的性质在子类中仍然成立,即:将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常。
- 里氏替换原则是实现开闭原则的重要方式之一。
- 依赖倒转原则
- 高层模块不应该依赖于低层模块。二者都应该依赖于抽象。
- 抽象不应该依赖于细节。细节应该依赖于抽象。
- 针对接口编程,不要针对实现编程。
- 接口隔离原则
- 避免接口污染
- 恰当的划分角色和接口
- 从一个客户类的角度来讲:一个类对另外一个类的依赖性应当是建立在最小的接口上的
- 使用多个专门的接口比使用单一的总接口要好
- 迪米特原则
- 又叫作最少知识原则(The LeastKnowledge Principle),一个类对于其他类知道的越少越好
- 就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。
- 组合/聚合/合成复用原则
- 尽量使用组合/聚合、尽量不使用继承
- 在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用这些对象的目的
- 面向接口编程的原则
- 面向接口编程,而不是面向实现编程。
- 可变性封装的原则
- 在设计时应当考虑系统中什么可能会发生变化或者什么特性具备多变的特征。这种变化不应该散落在代码中的各个角落,而是应该被适当的封装起来,以便于维护以及扩展;
所有设计原则的目标都是为了使软件减低耦合、增强灵活性。
单一职责:模块分层解耦。
这里使用接口实现也是可以的。
结果是得到一个单项关联(类和抽象类)和多个继承(抽象父类和它的子类)。
开闭原则:继承重写 / 接口实现。(相同模块类抽象封装)
里氏替换原则:继承重写。
依赖倒转原则:类的抽象封装。
接口隔离原则:顾名思义,隔离接口。
迪米特法则:减少类与类之间的关系。
合成复用原则:用组合代替继承。
软件设计模式
模式其实就是解决某一类问题的方法论。
把解决某类问题的方法总结归纳到理论高度,那就是模式。
模式对问题的描述以及对问题的解答应具有高度的抽象性和代表性。
模式是对现实生活某类现象的共同特质的高度抽象,描述了事务或者现象的规律,这种规律以及解决方法对于类似的现象同样有用。
设计模式使人们可以更加简单方便地复用成功的设计和体系结构。
设计模式提供了一种共享经验的方式,可以使团体受益和避免不断的重复发明。
在面向对象软件开发过程中,采用设计模式以复用成功的设计和体系结构。
设计模式具有简化软件系统的设计的优点。
模式的定义
每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心,通过这种方式,人们可以无数次地重用那些已有的解决方案,无须再重复相同的工作。
模式是在特定环境下人们解决某类重复出现问题的一套成功或有效的解决方案。
设计模式的分类
根据目的划分(模式是用来做什么的)可分为创建型,结构型和行为型三类。
- 创建型模式主要用于创建对象
- 结构型模式主要用于处理类或对象的组合
- 行为型模式主要用于描述类或对象如何交互和怎样分配职责
根据范围划分,即模式主要是处理类之间的关系还是处理对象之间的关系,可分为类模式和对象模式两种:
- 类模式处理类和子类之间的关系,这些关系通过继承建立,在编译时刻就被确定下来,是一种静态关系。
- 对象模式处理对象间的关系,这些关系在运行时变化,更具动态性
常见设计模式说明
创建型:用于创建对象。
简单工厂模式
简单工厂模式可以根据参数的不同返回不同的类的实例。
简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
简单工厂系统的扩展困难,一旦添加新的产品就不得不修改工厂逻辑,违背开闭原则。
在简单工厂模式中,如果需要增加新的具体产品,必须修改工厂类的源代码。
抽象工厂模式提供了一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
抽象工厂模式,当系统中有多于一个产品族时可以考虑使用抽象工厂模式。
抽象工厂模式,当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、更有效率。
抽象工厂模式,产品族是指位于不同产品等级结构、功能相关的产品组成的家族。
抽象工厂模式,一个具体工广可以创建出分属于不同产品等级结构的一个产品族中的所有对象。
例题:使用简单工厂模式模拟女娲(Nvwa)造人(Person),如果传入参数M,则返回一个Man对象,如果传入参数W,则返回一个Woman对象。现需要增加一个新的Robot类,如果传入参数R,则返回一个Robot对象,对该系统进行类图结构设计,用Java语言模拟实现该场景,并注意“女娲”的变化。
// 抽象类 Person
abstract class Person {
public abstract void show();
}
// Man 类
class Man extends Person {
@Override
public void show() {
System.out.println("This is a Man.");
}
}
// Woman 类
class Woman extends Person {
@Override
public void show() {
System.out.println("This is a Woman.");
}
}
// Robot 类
class Robot extends Person {
@Override
public void show() {
System.out.println("This is a Robot.");
}
}
// Nvwa 简单工厂类
class Nvwa {
public static Person createPerson(String type) {
switch (type) {
case "M":
return new Man();
case "W":
return new Woman();
case "R":
return new Robot();
default:
throw new IllegalArgumentException("Unknown type: " + type);
}
}
}
// 测试类
public class Main {
public static void main(String[] args) {
// 创建一个Man对象
Person man = Nvwa.createPerson("M");
man.show();
// 创建一个Woman对象
Person woman = Nvwa.createPerson("W");
woman.show();
// 创建一个Robot对象
Person robot = Nvwa.createPerson("R");
robot.show();
}
}
工厂方法模式
在工厂方法模式中引入了抽象工厂类,而具体产品的创建延迟到具体工厂中实现。
工厂方法模式添加新的产品对象很容易,无须对原有系统进行修改,符合开闭原则。
工厂方法模式存在的问题是在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,随着类个数的增加,会给系统带来一些额外开销。
工厂方法模式对应一个产品等级结构.而抽象工厂模式则需要面对多个产品等级结构。
工厂模式隔离产品的创建和使用。
在工厂类中封装产品对象的创建细节,客户类无须关心这些细节。
工厂方法模式中抽象工厂声明的工厂方法返回抽象产品类型,不能返回具体产品类型。
例题:现需要设计一个程序来读取多种不同类型的图片格式,针对每一种图片格式都设计一个图片读取器(ImageReader),如GIF图片读取器GifReader用于读取GIF格式的图片、JPEG图片读取器(JpgReader)用于读取JPEG格式的图片。图片读取器对象通过图片读取器工厂ImageReaderFactory来创建,ImageReaderFactory是一个抽象类,用于定义创建图片读取器的工厂方法,其子类GifReaderFactory和JpgReaderFactory用于创建具体的图片读取器对象。使用工厂方法模式实现该程序的设计并绘制相应的类图。
// 图片读取器接口
interface ImageReader {
void readImage();
}
// GifReader 类
class GifReader implements ImageReader {
@Override
public void readImage() {
System.out.println("Reading a GIF image.");
}
}
// JpgReader 类
class JpgReader implements ImageReader {
@Override
public void readImage() {
System.out.println("Reading a JPEG image.");
}
}
// 图片读取器工厂抽象类
abstract class ImageReaderFactory {
public abstract ImageReader createReader();
}
// GifReaderFactory 类
class GifReaderFactory extends ImageReaderFactory {
@Override
public ImageReader createReader() {
return new GifReader();
}
}
// JpgReaderFactory 类
class JpgReaderFactory extends ImageReaderFactory {
@Override
public ImageReader createReader() {
return new JpgReader();
}
}
// 测试类
public class Main {
public static void main(String[] args) {
// 创建 GIF 图片读取器的工厂
ImageReaderFactory gifFactory = new GifReaderFactory();
ImageReader gifReader = gifFactory.createReader();
gifReader.readImage();
// 创建 JPEG 图片读取器的工厂
ImageReaderFactory jpgFactory = new JpgReaderFactory();
ImageReader jpgReader = jpgFactory.createReader();
jpgReader.readImage();
}
}
结构型:描述类或者对象的组合。
适配器模式
例题一:我们有一个MediaPlayer接口和一个实现了MediaPlayer接口的实体类AudioPlayer。默认情况下,AudioPlayer可以播放mp3格式的音频文件。我们还有另一个接口AdvancedMediaPlayer和实现了AdvancedMediaPlayer接口的实体类。该类可以播放vlc和mp4格式的文件。我们想要让AudioPlayer播放其他格式的音频文件。为了实现这个功能,我们需要创建一个适配器类MediaAdapter,并使用AdvancedMediaPlayer对象来播放所需的格式。
package org.example;
interface MediaPlayer {
public void playMP3(String filename);
}
interface AdvancedMediaPlayer {
public void playMP4(String filename);
public void playVLC(String filename);
}
/**
* 使用适配器同时兼容两种播放器
*/
class MusicAdapter {
private MediaPlayer mediaPlayer;
private AdvancedMediaPlayer advancedMediaPlayer;
public MusicAdapter(MediaPlayer mediaPlayer, AdvancedMediaPlayer advancedMediaPlayer) {
this.mediaPlayer = mediaPlayer;
this.advancedMediaPlayer = advancedMediaPlayer;
}
public void play(String type, String fileSource) {
if (type.equals("mp3")) {
mediaPlayer.playMP3(fileSource);
} else if (type.equals("mp4")) {
advancedMediaPlayer.playMP4(fileSource);
} else if (type.equals("vlc")) {
advancedMediaPlayer.playVLC(fileSource);
} else {
System.out.println("你的格式不支持");
}
}
}
class MP3Impl implements MediaPlayer {
@Override
public void playMP3(String filename) {
System.out.println(filename + "正在播放");
}
}
class MP4VLCImpl implements AdvancedMediaPlayer {
@Override
public void playMP4(String filename) {
System.out.println(filename + "正在播放");
}
@Override
public void playVLC(String filename) {
System.out.println(filename + "正在播放");
}
}
public class Main {
public static void main(String[] args) {
MusicAdapter musicAdapter = new MusicAdapter(new MP3Impl(), new MP4VLCImpl());
musicAdapter.play("mp3", "mp3 filename");
}
}
组合模式
package org.zuhe;
import java.util.ArrayList;
import java.util.List;
// 需要有构造函数,所以这里需要抽象类,而不是接口
public abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
public abstract void operation();
public abstract void add(Component component);
public abstract List<Component> getChild();
public abstract void delete(Component component);
}
// 非叶子节点
class Composite extends Component {
List<Component> components = new ArrayList<>();
public Composite(String name) {
super(name);
}
@Override
public void operation() {
}
@Override
public void add(Component component) {
components.add(component);
}
@Override
public List<Component> getChild() {
return components;
}
@Override
public void delete(Component component) {
components.remove(component);
}
}
// 叶子节点
class Left extends Component {
List<Component> components = new ArrayList<>();
public Left(String name) {
super(name);
}
@Override
public void operation() {
System.out.println("叶子节点" + name + "的 operation 方法被调用");
}
@Override
public void add(Component component) {
components.add(component);
}
@Override
public List<Component> getChild() {
return components;
}
@Override
public void delete(Component component) {
components.remove(component);
}
}
class Client {
public static void main(String[] args) {
Composite root = new Composite("root");
Left left1 = new Left("left1");
Left left2 = new Left("left2");
root.add(left1);
root.add(left2);
root.delete(left2);
List<Component> child = root.getChild();
for (Component component : child) {
component.operation();
}
}
}
外观模式
例题:在计算机主机Mainframe中,只需要按下主机的开机按钮on(),即可调用其他硬件设备和软件的启动方法,如内存Memory的自检check(),CPU的运行run(),硬盘HardDisk的读取read(),操作系统OS的载入load()等,如果某一过程发生错误则计算机启动失败。使用外观模式模拟该过程,设计绘制类图并编程模拟实现
// CPU类
class CPU {
public boolean run() {
System.out.println("CPU运行...");
// 模拟成功的运行
return true;
}
}
// 硬盘类
class HardDisk {
public boolean read() {
System.out.println("硬盘读取...");
// 模拟成功的读取
return true;
}
}
// 内存类
class Memory {
public boolean check() {
System.out.println("内存自检...");
// 模拟成功的自检
return true;
}
}
// 操作系统类
class OS {
public boolean load() {
System.out.println("操作系统加载...");
// 模拟成功的加载
return true;
}
}
// 外观类
class Mainframe {
public void on() {
System.out.println("计算机启动中...");
boolean run = new CPU().run();
boolean read = new HardDisk().read();
boolean check = new Memory().check();
boolean load = new OS().load();
if (!run || !read || !check || !load) {
System.out.println("计算机启动失败");
} else {
System.out.println("计算机启动成功!");
}
}
}
public class Main {
public static void main(String[] args) {
// 创建计算机主机
Mainframe mainframe = new Mainframe();
// 启动计算机
mainframe.on();
}
}
行为型:描述类或者对象怎样交互和怎样分配职责。
观察者模式
例题:某在线游戏支持多人联机对战,每个玩家都可以加入某一战队组成联盟,当战队中某一成员受到敌人攻击时将给所有明友发送通知,盟友收到通知后将做出响应。使用观察者模式设计题目要求的系统功能结构类图,并编程模拟实现该过程。
// 被观察者接口
public interface Observer {
void update(String message);
}
// 被观察者类 - 团队成员
public class TeamMember implements Observer {
private String name;
public TeamMember(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " 收到了消息:" + message);
}
}
// 观察对象接口
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers(String message);
}
// 观察对象类 - 游戏团队
public class GameTeam implements Subject {
private List<Observer> observers = new ArrayList<>();
private String state; // 可能是玩家受攻击的信息等
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
public void setState(String state) {
this.state = state;
notifyObservers(state); // 当状态改变时通知所有观察者
}
}
public class Main {
public static void main(String[] args) {
GameTeam team = new GameTeam();
// 创建三个队员
TeamMember member1 = new TeamMember("小明");
TeamMember member2 = new TeamMember("小红");
TeamMember member3 = new TeamMember("小蓝");
// 注册观察者
team.registerObserver(member1);
team.registerObserver(member2);
team.registerObserver(member3);
// 模拟玩家受攻击
team.setState("玩家小明受到了敌人的攻击!");
}
}
策略模式
例题:一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:
public interface Strategy {
void show();
}
public class StrategyA implements Strategy {
@Override
public void show() {
System.out.println("StrategyA show");
}
}
public class StrategyB implements Strategy {
@Override
public void show() {
System.out.println("StrategyB show");
}
}
public class SalesMan {
private Strategy strategy;
public SalesMan(Strategy strategy) {
this.strategy = strategy;
}
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void salesManShow() {
strategy.show();
}
}
public class Main {
public static void main(String[] args) {
SalesMan salesMan = new SalesMan(new StrategyA());
salesMan.salesManShow();
salesMan.setStrategy(new StrategyB());
salesMan.salesManShow();
}
}
软件体系结构
核心概念
软件体系结构定义:软件系统的基本组织,包含构件、构件之间、构件与环境之间的关系(约束或配置),以及相关的设计与演化原则等。
- 构件用于实施计算和保存状态,是计算或数据存储的单元,可以是简单的或复合的
- 构件的定义:是指语义完整、语法正确和有可复用价值的单位软件,是软件复用过程中可以明确辨识的元素;结构上,它是语义描述、通讯接口和实现代码的复合体
构件是计算和数据存储的单元、构件是计算和状态的场所,构件可以是简单或者复合的
- 连接件用于表达构件之间的关系,是对构件之间的交互、交互的规则进行建模的体系结构元素
传统方法中,构件之间的连接关系并不独立存在,而是从属于构建,且表达能力较弱。
- 构件和连接件之间的匹配表示了系统的拓扑结构
- 软件体系结构=构件+连接件+约束(配置或匹配)
- Kruchten提出了一个 “4+1"视图模型” (The“4+1"View Model of Software Architecture),"4+1"视图,从5个不同的视角来描述软件体系结构。每一个视图只关心系统的一个侧面。
软件体系结构风格定义:软件体系结构风格是描述某一特定应用领域中系统组织方式的惯用模式。
软件体系结构风格包括如下关键要素:
- 一个术语词汇表
- 一组约束它们组合方式的规定
- 一个或多个语义模型,规定了如何从各成分的特性决定系统整体特性
常见件体系结构风格
经典软件体系结构风格:
- 调用返回风格:
。主程序子程序
。面向对象风格
。层次结构 - 以数据为中心风格:
。仓库系统
。黑板系统
数据库系统 - 独立构件风格:
。进程通讯
。事件系统 - 数据流风格:
。批处理序列
。管道过速器 - 虚拟机风格:
。解释器
。基于规则的系统
分布式风格:
- 模型视图控制器(MVC)风格
- 基于层次消息总线的体系结构风格
- 客户/服务器(C/S)体系结构风格
- SOA软件体系结构风格与Web Service
调用-返回体系结构风格
(主程序-子程序)
(数据抽象和面向对象)
(分层系统)
数据流体系结构风格
(批处理)
(管道-过滤器)
独立构件体系结构风格
(基于事件的隐式调用)
以数据为中心的体系结构风格
(黑板风格)
虚拟机体系结构风格
(解释器)
MVC体系结构风格
基于层次消息总线的体系结构风格
客户端\服务器(C/S)体系结构风格
SOA 软件体系结构风格
数据抽象与面向对象风格中,对象是构件,对象间的函数调用和过程调用是连接件。
管道-过滤器风格中,过滤器是构件,管道是连接件。
主程序/子程序风格中,主程序/子程序是构件,调用返回机制是连接件。
分布式软件体系结构风格:
- C/S风格、B/S风格
- MVC
- SOA 架构工作机制
C/S三层架构:表示层、功能层、数据层
MVC 优缺点:
优点:
(1)对于同一个模型,可以有不同的视图与控制器,以便提供给用户不同类型的用户图形界面。
(2)改变-传播机制保证了模型在改变的同时自动刷新所有的视图,所有的视图都同时实时地反映了模型的现有状态。
(3)MVC体系结构的设计使得改变用户图形界面变得非常容易,MVC架构非常适合业务逻辑较少改变,而用户图形界面需要经常改变的应用。
(4)由于全部的核心数据与核心功能都包含在模型(Model)中,因此很容易对核心的应用进行测试。
其他概念
软件系统的结构,包含软件元素、软件元素外部可见的属性以及这些软件元素之间的关系;
软件体系结构的发展过程经历了四个阶段:“无体系结构”阶段、萌芽阶段、初期阶段、高阶阶段。
软件体系结构的描述方法分为:图形表达工具、模块内连接语言、基于软件构建的系统描述语言、ADL。
软件体系结构建模的种类:结构模型、框架模型、动态模型、过程模型、
功能模型。
软件体系结构是对系统的高层设计,是从一个较高的层次来考虑组成系统的构件、构件之间的连接关系,以及系统需满足的约束等。
设计模式可以用于软件体系结构的设计,以实现体系结构级的设计复用。用于软件体系结构的设计模式通常称为架构模式或体系结构风格。
依据问题的规模或抽象层次,软件设计模式可分为三个层次:
- 架构模式:一种高层模式,用于描述系统级的结构组成、相互关系借相关约束。
- 设计模式:是中层模式,是针对系统局部设计问题给出的解决方案。(即通常所说的设计模式)
- 习惯用法:通常被认为是与具体编程语言相关的一种底层模式,习惯用法给出的解决方案通常与具体的编程语言的某种语法机制相关。
表达构件之间关系的连接器分离出来,作为同构件平等的实体:
- 连接器可能要表达构件之间相当复杂的关系语义,需要详细的定义和复杂的规约
- 复杂连接器的定义应当局部化,而不是分散定义在多个构件中,以保证系统具有良好的结构
- 构件之间的关系并非是固定不变的,有可能随着系统的运行需要动态改变,这种改变应封装在连接器中
- 连接器和构件独立且有分工。构件应该只定义它的能力(包括功能和非功能两个方面);连接器规定构件之间的交互
- 系统开发时常常复用已有的连接器,例如过滤器、客户服务器协议、数据库访问协议等
鉴于是否有一个稳定的软件体系结构对软件的质量和成本影响很大,因此如何获得一个良好的体系结构就成为当今软件界研究的重点。
软件体系结构风格,反映了领域中众多系统所共有的结构和语义特征,并指导如何将各个模块和子系统有效地组织成一个完整的系统。
描述一类体系结构(如层次风格),在实践中被多次设计、应用(层次风格几乎已经成为企业信息化必须遵循的系统风格),是若干设计思想的综合,具有已经被熟知的特性,并且可以被复用。
软件体系结构风格的重要作用:
- 促进了设计的复用,使得一些经过实践证实的解决方案能够可靠地解决新问题。
- 能带来显著的代码复用,使得体系结构风格中的不变部分可共享同一个解决方案。
- 便于设计者之间的交流与理解。
- 通过对标准风格的使用支持了互操作性,以便于相关工具的集成。