软件工程-结构化方法

349 阅读29分钟

三、结构化方法

结构化方法
结构化分析是一种面向数据流的传统软件开发方法,它以数据流为中心构建软件的分析模型和设计模型。
结构化分析、结构化设计和结构化程序设计,构成了完整的结构化方法。

结构化分析

结构化分析(Structured Analysis,SA)
结构化分析是一种系统开发方法论,其核心是在软件工程早期阶段严谨地发现、表达和确认系统需求。

1.发现需求

需求分类

1.业务需求(Business Requirements):关注组织层面的目标和策略要求。
2.用户需求(User Requirements):反映直接使用系统的终端用户的需求和期望。
3.系统需求(System Requirements)。(1)功能需求:描述系统需要完成的任务或服务。。(2)非功能需求:包括性能指标、安全性、可用性等技术约束,以及诸如开发平台选择(例如这里指定为Java,设计约束)等实施限制。
按MoSCoW法则
Must Have(必须有):基本需求,重要但不阻止发布的可接受需求。
Should Have(应该有):期望需求,重要但不阻止发布的可接受需求。
Could Have(可以有):当前迭代周期内明确排除的需求。
Won't Have(本次不会做):当前迭代周期内明确排除的需求。
兴奋需求(有时也被提及):能够显著提升用户体验或创造竞争优势的需求。

需求获取方法

需求获取方法
收集资料
用户访谈

2求精

求精
针对初步需求进行多次迭代和细化,确保需求的清晰度和完整性。

3.建模

建模
建立模型,用图形符号和组织规则书面描述事物。
1.模型核心:数据字典,提供所有数据元素的详细定义和属性。2.数据模型:采用实体关系图(ER图)展示数据之间的结构关系。3.功能模型:使用数据流图(DFD)来直观描绘数据如何在系统各组件之间流动及处理过程。4.行为模型:利用状态转换图(STD)来阐述系统的动态行为,即在不同事件驱动下状态的改变。
数据字典提供数据的详细定义,ER图展示数据结构,DFD勾勒数据处理流程,状态转换图则描绘系统的动态行为。

4.需求规格说明

规格说明
编写详细的软件需求规格说明书,作为结构化分析阶段的最终成果,它是后续设计和实现工作的基础。
有模板。

5.复审

复审
对上述所有工作成果进行审查验证,确保需求理解一致、模型无误,规格说明文档满足各方共识,并为下一阶段的工作做好准备。

结构化设计

结构化设计(Structured Design,SD)。

1.设计原则

结构化设计遵循的原则
1.自顶向下设计:从整体出发,首先定义系统的顶层架构和主要功能模块,然后逐层细化直至最小可执行单元。
2.逐步求精:将复杂的系统逐步细化成若干层次的子系统和模块,每一步都比前一步更加详细和具体。
3.模块化:将系统划分为多个模块,每个模块具有明确的功能,模块间通过接口进行通信,模块内部相对独立。
4.提高抽象层次。
模块设计
模块化设计要求高内聚、低耦合,模块独立体现的就是高内聚低耦合。
1.模块独立 。模块的独立性原则表现在模块完成独立的功能,与其他模块的联系应该尽可能 得简单,各个模块具有相对的独立性。模块独立(高内聚、低耦合、复杂度)
2 .模块的规模要适当。模块的规模不能太大,也不能太小。如果模块的功能太强,可读性就会较差, 若模块的功能太弱,就会有很多的接口。读者需要通过较多的程序设计来进行 经验的积累。
3.分解模块时要注意层次。在进行多层次任务分解时,要注意对问题进行抽象化。在分解初期,可以只考虑大的模块,在中期,再逐步进行细化,分解成较小的模块进行设计。
4.多扇入 (Many-to-One Fan-in): 多扇入指的是一个模块可以接收来自多个其他模块的数据或者控制信号。少扇出 (One-to-Many Fan-out): 少扇出是指一个模块向多个其他模块发送数据或控制信号的情况。
5.尽可能减少调用的深度。深度和宽度合理。
6.单入口,单出口。
7.模块的作用域应该在模块之内。
8.功能应该是可预测的。

内聚

内聚(Cohesion)
内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐藏和局部化概念的自然扩展。
功能
功能内聚(Functional Cohesion)
最强的内聚,模块内的所有元素共同作用完成一个功能,缺一不可。
顺序
顺序内聚 (Sequential Cohesion)
顺序内聚是指模块内部的各个组成部分(如函数、过程或操作)按照严格的逻辑顺序执行,并且每一部分的输出直接成为下一部分的输入,它们共同完成一个单一的功能。这种内聚形式就像是装配线上的工作流程,每个步骤依赖前一步骤的结果。

示例场景:假设有一个模块负责处理用户登录的过程,其内部顺序如下:

  1. readUserInput(): 读取用户在界面上输入的用户名和密码。
  2. validateCredentials(): 验证输入的用户名和密码是否匹配系统存储的合法用户数据。
  3. generateSessionToken(): 如果验证通过,则生成一个会话令牌。
  4. persistSessionData(): 将会话令牌保存到服务器端的会话存储中。
  5. sendResponseToClient(): 向客户端发送登录成功响应,包含生成的会话令牌。

在这个例子中,每个函数都是登录流程的一个必要环节,它们必须按照特定顺序执行,且每个步骤的输出(例如验证结果或会话令牌)是后续步骤的输入,从而构成了典型的顺序内聚模块。

通信
通信内聚 (Procedural/Information Cohesion)
通信内聚(有时也被称为信息内聚或数据内聚)是指模块内部的各个组成元素虽然执行不同的操作,但它们都操作相同的数据结构或者基于同一个输入数据集来完成各自的工作,最终可能生成与该数据集相关的同一种输出结果。这种内聚强调的是模块内部的各个部分围绕着对共享数据的操作而组织起来。

示例场景: 考虑一个银行账户管理模块,其中包含以下两个子功能:

  1. calculateInterest(): 根据账户余额和利率计算并更新账户的利息。
  2. generateStatement(): 使用账户的基本信息(如账户号、姓名、余额及新增加的利息)生成账户的月度对账单。

在这一模块中,尽管calculateInterest()generateStatement()执行的任务不同,但它们都紧密地围绕着同一个数据实体——银行账户及其相关信息进行操作。这两个功能都需要访问和修改账户的同一组数据,因此,这个模块体现了良好的通信内聚性。

过程
过程内聚 (Process Cohesion)
模块内部的多个操作步骤按照严格的逻辑顺序执行,这些步骤共同完成一个单一的任务,而且每个步骤的结果通常是下一个步骤的输入或其执行的必要条件,即便它们之间没有直接的数据传递。
过程内聚关注的是控制流上的顺序性和整体性,而顺序内聚更强调由于数据依赖关系形成的自然执行顺序。
时间
时间内聚 (Temporal Cohesion)
时间内聚是指模块内部包含的任务或操作必须在同一时间段内执行,即使这些任务在功能上彼此关联性不强。这类模块的设计依据是时间窗口而非功能相关性。

例子: 假设有一个系统维护模块,它负责每天凌晨零点执行一系列的后台维护任务:

  1. backupDatabase(): 备份整个数据库到磁带或云存储。
  2. clearCache(): 清除系统缓存,以便新的一天开始时加载最新的数据。
  3. analyzeSystemLogs(): 分析前一天的日志文件,检查是否有异常事件或性能瓶颈。
  4. generateDailyReport(): 基于系统活动数据生成前一天的运营报告。

这些任务虽然在功能上互不相关,但它们都被安排在一天之始的特定时间段内执行,因为这时系统负载较低,适合进行批量处理和维护操作。由于所有任务都在同一时间段内完成,所以这个模块就表现出了时间内聚的特性。

逻辑
逻辑内聚 (Logical Cohesion)
逻辑内聚是指模块内部的元素基于逻辑上的相关性而组合在一起,即它们执行相似或者相关的功能,但在功能实现的具体步骤上可能有所不同。

例子: 设想一个简单的图形用户界面(GUI)组件库中的“图形形状绘制”模块,该模块包含以下几个子函数:

  1. drawCircle(x, y, radius): 绘制一个圆,参数包括圆心坐标(x, y)和半径radius。
  2. drawRectangle(x1, y1, x2, y2): 绘制一个矩形,参数包括左上角坐标(x1, y1)和右下角坐标(x2, y2)。
  3. drawLine(startX, startY, endX, endY): 绘制一条从起点(startX, startY)到终点(endX, endY)的直线。
  4. drawTriangle(x1, y1, x2, y2, x3, y3): 绘制一个三角形,参数为三个顶点坐标。

尽管每个函数分别绘制不同的几何图形,它们都属于图形绘制这一逻辑范畴,因此在这个模块中共同存在是基于逻辑内聚原则。每一个函数都是独立的功能单元,它们之间共享的是高层次的抽象概念——“图形绘制”,而不是具体的实现细节。这种情况下,模块内的各个组成部分具有较高程度的逻辑相关性。

偶然内聚 (Coincidental Cohesion)
偶然内聚是指模块内部的元素由于历史、巧合或其他无明显逻辑关系的原因放在一起,这些元素在功能或目的上缺乏直接关联。这是模块内聚度最低的形式。

例子:

假设在一个大型软件系统中有一个名为AdminUtilities的模块,它包含了几个看似不相关的功能函数:

  1. sendEmailNotification(user, subject, message): 发送电子邮件通知给指定用户。
  2. calculateTax(income, taxRate): 根据收入和税率计算税金。
  3. archiveOldRecords(dateLimit): 将所有早于特定日期的记录存档。
  4. generateMonthlyReport(): 生成月度销售报告。

这些函数之所以被放在同一个模块中,并不是因为它们在逻辑上有紧密联系,而是可能因为开发时碰巧都在同一时期完成,或者是早期设计时为了方便临时存放而归在同一模块下。这些函数之间不存在明显的功能性关联,任意一个函数都可以在其他上下文中独立使用,因此这个模块就表现为偶然内聚

耦合

非直接
无直接耦合(No coupling 、 Non-coupling)
无直接耦合指的是模块之间不存在任何依赖关系,即一个模块的变化不会影响到另一个模块的行为或状态,它们彼此完全独立。这种耦合程度是最理想的,有助于提高系统的可重用性和可维护性。

例子:

假设我们有两个软件模块,ModuleA负责处理员工工资计算,而ModuleB则负责管理库存系统。

  1. ModuleA包含用于计算工资、扣除税费和生成工资单的功能。
  2. ModuleB包含商品库存数量管理、进货出货记录以及库存预警等功能。

在这一场景中,ModuleAModuleB之间没有直接耦合关系,也就是说,修改ModuleA中的工资计算逻辑不会对库存管理产生任何影响,反之亦然。这两个模块各自独立运行,通过良好的模块化设计实现了各自的业务目标,且相互之间无需了解对方的具体实现细节。

数据
数据耦合(Data Coupling)
数据耦合是模块间耦合度较低的形式,其中一个模块向另一个模块传递数据,仅通过参数传递最必要的数据值,不涉及其他形式的相互依赖。
// 定义一个表示员工的类
public class Employee {
    private String name;
    private double salary;
​
    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
​
    // 提供获取员工薪水的方法
    public double getSalary() {
        return salary;
    }
}
​
// 定义一个处理薪资计算的类
public class TaxCalculator {
    // 计算所得税的方法,仅接受员工薪水作为参数
    public double calculateIncomeTax(double salary) {
        // 假设这里是一个简化版的税率计算逻辑
        double taxRate = 0.2; // 假设税率为20%
        return salary * taxRate;
    }
}
​
// 使用
public class Main {
    public static void main(String[] args) {
        // 创建一个员工对象
        Employee john = new Employee("John Doe", 5000.0);
​
        // 创建一个税务计算器对象
        TaxCalculator calculator = new TaxCalculator();
​
        // 通过传递员工薪水数据来计算所得税
        double taxAmount = calculator.calculateIncomeTax(john.getSalary());
​
        System.out.println("John Doe's income tax amount is: " + taxAmount);
    }
}
在这个Java示例中,TaxCalculator 类的 calculateIncomeTax 方法与 Employee 类之间是数据耦合。TaxCalculator 只关心接收一个薪水数字作为参数来计算税款,而不关心这个数字是从哪里来的或与何种对象有关联。因此,这两个类之间只有数据层面的耦合,这符合低耦合设计的原则。
标记
标记耦合(Stamp Coupling)
标记耦合是指模块之间通过传递复杂数据结构(如对象或记录)作为参数,其中模块只需使用结构中的部分内容,而非全部。这种耦合形式要求调用模块了解数据结构的所有字段,即使它只使用了其中的一部分。
// 定义一个复杂的员工记录结构
public class EmployeeRecord {
    private int id;
    private String name;
    private double salary;
    private Address address;
    private List<WorkExperience> workExperiences;
​
    // 构造函数、getters 和 setters 等...
}
​
public class Address {
    private String street;
    private String city;
    private String state;
    private String zipCode;
​
    // 构造函数、getters 和 setters 等...
}
​
public class WorkExperience {
    private String companyName;
    private String position;
    private Date startDate;
    private Date endDate;
​
    // 构造函数、getters 和 setters 等...
}
​
public class ReportGenerator {
    // 这个方法只关心员工姓名和薪水,但需要整个员工记录作为参数
    public void generateSalaryReport(EmployeeRecord employee) {
        String name = employee.getName();
        double salary = employee.getSalary();
​
        // 生成薪资报告,仅使用了员工记录的部分信息
        System.out.println("Name: " + name + ", Salary: " + salary);
    }
}
​
public class Main {
    public static void main(String[] args) {
        EmployeeRecord john = new EmployeeRecord(/*...构造完整员工记录...*/);
​
        ReportGenerator reportGen = new ReportGenerator();
        reportGen.generateSalaryReport(john);  // 传递整个员工记录,但实际上只使用了name和salary属性
    }
}
在上述Java示例中,ReportGenerator 类的 generateSalaryReport 方法与 EmployeeRecord 类之间存在标记耦合。尽管该方法只需要员工的名字和薪水信息来生成报告,但它却接收了整个 EmployeeRecord 对象作为参数,这意味着它不得不了解这个记录的所有字段。当 EmployeeRecord 结构发生变化时,只要 name 和 salary 字段不变,generateSalaryReport 方法仍可以正常工作;但如果这些字段位置或名称改变,或者增加了额外的逻辑依赖,那么 ReportGenerator 类就需要相应地更新。
控制
控制耦合(Control Coupling)
控制耦合指的是一个模块通过传递条件、标志或命令来控制另一个模块的行为。
若模块 A 通过控制参数来传递信息给模块 B,从而确定执行模块 B 中的那部分语句。则这两个模块的耦合类型是控制耦合。
class Printer {
public:
    void printLine(const std::string& line, bool bold) {
        if (bold) {
            // 控制耦合:外部模块通过布尔值决定是否以粗体打印
            printInBold(line);
        } else {
            printNormal(line);
        }
    }
​
private:
    void printInBold(const std::string& line) {
        // 实现粗体打印的逻辑
        std::cout << "[BOLD] " << line << std::endl;
    }
​
    void printNormal(const std::string& line) {
        // 实现正常打印的逻辑
        std::cout << line << std::endl;
    }
};
​
int main() {
    Printer printer;
    bool shouldPrintBold = true;
​
    // 控制耦合:主函数通过布尔标志控制打印样式
    printer.printLine("Hello, World!", shouldPrintBold);
​
    return 0;
}
在这个例子中,Printer 类有一个 printLine 方法,它接受一条字符串和一个布尔值作为参数。布尔值决定了是否要以粗体打印这条字符串,因此外部模块(这里是 main 函数)通过传递不同的布尔值来控制 Printer 模块的行为,形成了控制耦合。
外部
外部耦合(External Coupling)
外部耦合是指两个或多个模块直接访问相同的全局变量或外部资源(如文件、数据库、硬件设备等),而不是通过函数参数或返回值进行交互。在编程实例中,这意味着模块间的联系不是通过显式的接口传递数据,而是通过全局可见的变量来间接关联。这会增加模块之间的耦合度,因为修改全局变量会影响所有依赖它的模块。
// 全局变量声明,在所有模块都能访问的地方(比如在一个头文件global.h中)
extern int globalValue;
​
// 模块A
void moduleA() {
    // 操作全局变量
    globalValue = 10;
}
​
// 模块B
void moduleB() {
    // 读取全局变量并做相应操作
    if (globalValue > 5) {
        printf("Global value is high from module B's perspective.\n");
    }
}
​
// 主程序
#include "global.h"int main() {
    moduleA();
    moduleB();
    return 0;
}
​
// 全局变量定义(在某个.c文件中)
int globalValue; 
公共
公共耦合(Common Coupling)
公共耦合(Common Coupling)是指多个模块直接访问同一份公共数据区域,例如全局变量、共享内存区域、数据库中的公共表、或者是系统范围内的某种状态信息。当这些模块修改或依赖于这个公共数据环境时,它们之间就形成了公共耦合。公共耦合会使得模块间的关系变得复杂,因为任何对公共数据的修改都可能潜在地影响到所有引用该数据的模块。
外部耦合更广泛地涵盖了模块与外部世界的所有依赖关系,而公共耦合特别指出了因共享公共数据而导致的模块间高度耦合的情况。
// 假设有一个全局变量在全局头文件global.h中声明
// 全局头文件global.h
#ifndef GLOBAL_H
#define GLOBAL_H
extern int sharedData;
#endif// 模块A.c
#include "global.h"
void moduleA() {
    // 修改共享数据
    sharedData = 100;
    printf("Module A set sharedData to %d\n", sharedData);
}
​
// 模块B.c
#include "global.h"
void moduleB() {
    // 访问和使用共享数据
    printf("Module B sees sharedData as: %d\n", sharedData);
    // 可能还会基于sharedData做出进一步的操作
}
​
// 主程序main.c
#include "global.h"
int sharedData; // 全局变量的定义int main() {
    moduleA();
    moduleB();
    return 0;
}
在这个例子中,模块A和模块B都直接访问并修改全局变量sharedData,这就构成了公共耦合。如果将来需要更改sharedData的数据类型、初始化方式或其在整个程序中的作用,那么可能需要同时修改这两个模块的代码。此外,由于没有明确的接口控制sharedData的访问和修改,所以难以保证数据的一致性和安全性。为了降低耦合度,通常建议使用更严格的封装和依赖注入等技术来代替全局变量。
内容
内容耦合(Content Coupling)
内容耦合是模块间耦合度最高的一种形式,表现为一个模块直接访问或修改另一个模块的内部数据,或者直接跳转至另一个模块的内部逻辑。这种耦合形式使得模块间的边界模糊,难以独立测试和复用。
public class ModuleA {
    private int internalValue; // 模块A的内部数据成员
​
    public void setInternalValue(int value) {
        internalValue = value;
    }
    
    // 其他方法...
}
​
public class ModuleB {
    public void processAndModifyA(ModuleA moduleA) {
        // 直接访问和修改模块A的内部数据
        moduleA.setInternalValue(moduleA.getInternalValue() * 2); 
​
        // 或者更严重的内容耦合形式,直接调用模块A的内部逻辑
        // 假设有这样一个私有方法
        // private void internalMethod() {...}
        // 在模块B中不当访问
        moduleA.internalMethod(); 
    }
}
​
public class Main {
    public static void main(String[] args) {
        ModuleA a = new ModuleA();
        ModuleB b = new ModuleB();
​
        // 导致内容耦合的操作
        b.processAndModifyA(a);
    }
}
在这个Java示例中,ModuleB 的 processAndModifyA 方法直接修改了 ModuleA 中的内部变量 internalValue,并且如果存在的话,还直接调用了 ModuleA 的私有方法 internalMethod()。这就构成了内容耦合,因为 ModuleB 依赖于 ModuleA 的内部实现细节来完成其功能,违反了封装原则,降低了代码的可读性和可维护性。

概要设计

软件系统总体结构设计,将系统划分成模块;确定每个模块的功能;确定模块之间的调用关系;确定模块之间的接口,即模块之间传递的信息;评价模块结构的质量。

数据结构及数据库设计。

模块控制域:这个模块本身以及所有直接或间接从属于它的模块的集合。

模块作用域:指受该模块内一个判定所影响的所有模块的集合。

模块的作用域应该在控制域范围之内。

详细设计

软件详细设计阶段的主要任务包括:对模块内的数据结构进行设计;对数据库进行物理设计;对每个模块进行详细的算法设计;代码设计、输入/输 出设计、用户界面设计等其他设计。

结构化程序设计

结构化程序设计(Structured Programming,SP)
根据语言特性。

数据字典

blog.csdn.net/m0_63222058…

数据字典 (Data Dictionary,DD)
据字典是对数据流图中出现的所有数据元素、数据结构、数据流、文件和数据存储的详细说明。它为DFD中的每个数据项提供了精确、一致和完整的定义,确保各方对系统数据的理解统一。
数据字典是对数据流图中出现的全部被命名的图形元素在数据字典中作为一个词条加以定义,使每一个图形元素的名称都有一个确切的解释。
数据字典包含 4 类条目:数据流、数据项、数据存储和基本加工。
数据流 (Data Flow)
描述数据在系统内部从一个地方流向另一个地方的过程,包括数据流的名称、组成的数据项、数据流的源头(加工或外部实体)和目的地(加工或外部实体)。
数据项 (Data Item)
是数据流或数据存储的基本组成单位,描述的是单个数据元素的特性,包括数据项名称、定义、数据类型、长度、格式、取值范围、是否允许空值以及其他相关属性。
数据存储 (Data Storage)
也称为数据文件或数据库,指系统中持久保存数据的地方,包括数据存储的名称、内容(包含哪些数据项)、更新方式、存取频率及相关的完整性约束等。
基本加工/数据处理(Data Process)
其中基本加工条目是用来说明DFD 中基本加工的处理逻辑的,由于下层的基本加工是由上层加工分解而来,只要有了基本加工的说明就可理解其他加工。对每一个基本加工,都应该有一个加工逻辑来说明。
加工描述了输入数据流到输出数据流之间的编号,也就是输入数据流经过什么处理后变成 了输出数据流。
加工逻辑是位于需求分析阶段,此时具体的数据结构和算法并没有进行设计。
常用的加工逻辑描述方法有结构化语言、判定表和判定树三种。

实体-关系图

实体关系图 (Entity-Relationship Diagram, E-RD)

数据流图

数据流图 (Data Flow Diagram, DFD)
数据流图是一种用于表示信息系统逻辑模型的图形工具,主要用于结构化系统分析和设计阶段。DFD从数据处理的角度出发,清晰直观地展示了系统内部的数据流动情况,以及数据在系统不同处理单元之间的流动路径和转换过程。这种图示方法不涉及具体的物理实现细节,而是着重于系统功能的抽象描述。
数据流图描述的是做什么,而不是怎么做。

1.组成元素

外部实体

外部实体(External Entity)
外部实体又称数据源或数据终点,用长方形框表示,代表系统边界之外的实体,它们可以是人、其他系统(第三方系统)、组织(组织机构)或者设备,它们与系统之间交换数据。

数据流

数据流(Data Flow)
表示数据在系统内从一个地方移动到另一个地方的过程,通常用箭头表示,箭头上的标签注明数据流的名称和内容。
由一组固定成分的数据组成,表示数据的流向。
每个数据流通常有一个合适的名词反映数据流的含义。不用缺乏具体含义名字,如“数据”、“信息”。

数据处理

数据处理(Data Process)
数据处理也称为数据加工或转换,用圆圈或矩形框表示,表明数据在其中发生操作或变换的过程。
数据加工主谓结构,名词+动词。避免使用“加工”、“处理”等笼统动词。应反映整个处理的功能,不是一部分功能。通常仅包括一个动词,否则分解。
对每一个基本加工,必须有一个加工规格说明。加工规格说明必须描述把输入数据流变换为输出数据流的加工规则。加工规格说明不需要给出实现加工的细节。结构化语言、决策树、决策表可以用来表示加工规格说明。在有些情况下,数据流图中某个加工的一组动作依赖于多个逻辑条件的取值。这时,用自然语言或结构化语言都不易于清楚地描述出来,而用判断表。
一个加工不适合有过多的数据流。分解尽可能均匀。
加工逻辑描述
结构化语言
if (若)
else(否则)
endif(结束)
    
then(则)

数据存储

数据存储(Data Store)
数据存储又称为文件或数据仓库,用双水平线表示,代表系统中的静态数据集合,存放着在系统运行过程中暂时或永久存储的数据。
用来表示暂时存储的数据,每个文件都有名字。流向文件的数据流表示写文件,流出的表示读文件。
结合数据流上的名词与文字信息确定数据存储名称。命名表、信息表、记录、文件等关键字均可。

2.分层数据流图

分层数据流图(Hierarchical Data Flow Diagram,HDFD)
DFD通常是分层绘制的,高层次的数据流图概括了系统的整体逻辑结构,随着层次逐渐深入,每一层的DFD都会揭示更多的细节。这样可以逐步分解复杂系统,帮助系统分析员、设计师和最终用户更好地理解和沟通系统的逻辑功能需求。
在分层数据流图中,通常包括以下层次,顶层数据流图、中间层数据流图、底层数据流图。

DFD-Layered

数据流图平衡原则

blog.csdn.net/weixin_4103…

数据流图平衡原则
在构建分层数据流图时,需要注意保持子图与父图之间的平衡,即子图的输入和输出数据流应当与父图中对应加工的输入和输出数据流一致,确保数据在各层次间的流动和处理能够准确无误地衔接。
如何保持数据流平衡
① 父图中加工的输入输出数据流要和子图中加工的输入输出数据流的名字和数量要一致。
②父图中一个输入(或者输出)数据流组对应着子图多个输入(或输出的)数据流。
③子图中组合这些数据流的数据项恰好是父图这一条数据流。
父图与子图
父图与子图之间的平衡
意味着任何一个DFD子图的边界上的输入/输出数据流必须与其父图中对应的加工(Process)的输入/输出保持一致。具体而言,子图的输入数据流应与父图中流向该子图对应加工的输入数据流匹配,子图的输出数据流应与从该子图对应加工流出到父图的输出数据流相匹配。这一原则有助于验证数据流是否在分解过程中被遗漏或错误地重新分配,确保数据的流向和处理过程在不同抽象层级上保持连贯。
从外部实体输入的数据流和输出到外部实体的数据流的个数、方向要保持一致 。
子图内部的平衡
守恒加工原则
守恒加工原则
加工的输入和输出应该匹配,避免出现“黑洞”(只有输入没有输出)、“奇迹”(只有输出没有输入)或“灰洞”(输入不足以产生指定的输出)的情况,保证数据处理的逻辑合理性。
1.黑洞:指加工只有输入数据流而没有相应输出的情况,这在逻辑上是不可接受的,因为数据不应无故消失。
2.奇迹:指加工只有输出数据流而没有输入的情况,这意味着数据似乎凭空产生,同样违背了逻辑。
3.灰洞:加工中的输入数据流不足以产生所列出的输出数据流,或没有足够的信息说明如何从输入生成输出,这表示数据处理逻辑不完整或描述不清。假设我们有一个加工块,输入包括“学生信息”和“考试成绩”,输出包括“学生健康状态”。在这个加工块中,输入数据流包括“学生信息”和“考试成绩”,但输出的“学生健康状态”并不是基于这两个输入数据流计算得到的。这就导致了一个灰洞。
注意:1.看加工一个输入流经过加工后完全可以至少产生一个或多个输出流。
数据守恒原则
数据守恒原则
数据守恒原则,要求对于任何加工,其输出数据流中的所有数据必须能够直接从其输入数据流中获得,或者是通过该加工逻辑自然生成的。这意味着数据不能无中生有,也不能在处理过程中无故消失,确保了数据处理的逻辑严密性。
数据流的起点和终点必须要有一个是加工:1.外部实体与外部实体之间不存在数据流。2.外部实体与数据存储之间不存在数据流。3.数据存储与数据存储之间不存在数据流。
缺失数据流分析(看分数,4分缺失四条)
1.如果未从父图与子图之间的平衡分析出缺失的数据流,则子图中就不可能缺失流向外部实体的输出或输入数据流。2.根据子图内部的平衡和数据守恒原则,缺失的数据流只可能是数据存储与数据加工(读) 之间、数据加工与数据加工之间。3.看每一句话,在数据流图中有没有体现,数据流是名词,数据加工是名词+动词。

编号

编号
在实际应用中,每层DFD中的各个子图都会被赋予编号,便于管理和追踪,同时也方便团队成员识别和讨论各个层级的系统功能细节。通过这种方式,分层数据流图能够有效地支持系统分析和设计的迭代细化过程。

用途

局部数据存贮:在子图中出现的数据存贮,可以不出现在父图中,画父图时只需画出处理逻辑之间的联系,不必画出各个处理逻辑内部的细节。

用途
作为交流信息的工具。
作为分析和设计的工具:用数据流图辅助物理系统设计时,可在数据流图上画出许多组自动化边界,每组自动化边界可能意味着不同的物理系统。

3.例题

DFD-exercise

DFD-data flow

数据流名称起点终点
地块信息D2P5
数据流组成示例
1.农事信息请求 = 账号 + 密码 + 查询条件
2.农事信息请求 = 账号 + 密码 +0{查询条件}*
3.农事信息请求:账号、密码、查询条件

状态转换图

状态转换图(State Transition Diagram, STD)
  • 状态(State):表示系统可能处于的一个条件或行为模式,在某一特定时间点系统可以处于且只能处于一个状态。
  • 事件(Event):是引起状态变迁的外部或内部信号。当事件发生时,系统从一个状态转移到另一个状态。
  • 变迁(Transition):由一条箭线表示,箭头从一个状态指向另一个状态,表明当某个事件发生时,系统会从源状态迁移到目标状态。
  • 动作(Action):伴随状态变迁发生的操作,可以是计算、数据处理、消息发送等行为。
  • 初始状态(Initial State):通常标记为实心圆圈,表示系统开始时所处的状态。
  • 终止状态(Final State):也称为结束状态,标记为一个实心圆圈外面加上一个圆环,表示系统生命周期中的一个终结点或完成状态。