构建向导描述的逻辑层






4.80/5 (21投票s)
本文描述了用于描述向导的逻辑层的构建和使用方法。
图 1 测试应用程序的面板。
引言
在编写一个用于分步编辑和维护复杂文档的程序时,我产生了一个想法,能够以简单而有效的方式解决这个问题。
过了一段时间,我意识到我解决特定问题的方式代表了一种更广泛问题的通用方法,因此可能对其他人有用。这就是我决定为您的网站写一篇短文来分享这个想法的原因。考虑到关于编程艺术的论文不多,这尤其合适。
我将要处理的一类问题称为向导。从某种意义上说,任何安装过程都是向导的最简单示例。用户需要经历一系列对话框,选择相关选项并运行安装程序。示意性地,这一系列事件可以描绘成下图:
向导允许用户跳回一个或多个步骤,以便在需要时重新执行它们。
本质上,还有许多其他过程可以称为向导,并由类似于图 2 的方案表示。举几个例子,我们提到以下过程:
- 填写表单和执行数据管理中的复杂事务,
- 测试和设置众多设备,
- 校准测量设备。
这个列表并不完整,读者可以轻松地添加自己的示例来继续。
更一般地说,一个向导类型的问题可以表述如下:存在一组任务;它们按特定顺序执行,该顺序由特定应用程序规定的某些逻辑考虑决定。
重要的是,向导不同于常规的流程图。流程图的引擎会验证任务是否可以执行;仅当任务可以执行时,它才会自动运行任务。例如,如果一个任务是某个函数,那么一旦提供了函数参数,该函数就会被执行。
向导有一个独特的特点:任务不是自动执行的;它们由应用程序用户手动运行,而向导的引擎只是在每个时间实例检测哪些任务可供运行,并允许用户运行它们。但是,最终决定执行哪些任务以及何时执行的是用户。
让我们考虑图 3 所示的过程。它由 5 个任务组成。请注意,任务 1 到任务 3 不依赖于其他任务,因此可以随时以任何顺序执行。任务 4 只能在任务 1 和任务 2 都完成后才能执行。任务 5 在任务 3 和任务 4 完成后即可运行。
如果此时要运行任务 2,那么任务 4 和任务 5 也可以重新执行。需要注意一个重要说明:如果一个任务被重新执行,并且存在其他任务的结果依赖于该任务,那么这些其他任务也必须被重新执行。这种问题也属于向导类型,尽管它更复杂一些。在每一步,用户都关心两个问题:
- 哪些任务可以(或应该)完成?
- 还有哪些任务尚未完成?
程序员的问题更为复杂:他/她应该知道如何实现这种逻辑?通常,一切都以传统方式完成。在每一步,都会分析当前进程的状态并做出决策。例如:
if (IsDone(Task1) && IsDone(Task2) &&
IsDone(Task3) && IsDone(Task4) && !IsDone(Task5))
{
…
Done(Task5);
…
}
如果任务数量庞大,并且它们之间的链接很复杂,那么提出的解决方案将变得令人绝望地麻烦。此外,逻辑上的一点小改动都会导致需要对代码进行重大修改。调试本身就是一个问题……
有没有办法让我们的生活更轻松?我认为有一种方法可以节省时间。我将在下一节进行解释。我只想说,我的解决方案绝不是唯一的解决方案。
计算模型
将任务表示为类似于图 3 的图表是构建计算模型的起点。任何任务都可以表示为一个功能块,见图 4。
该框代表一个任务,具有两个主要属性:
- 输入条件列表。
- 输出条件列表。
输入条件由布尔变量向量给出。如果所有变量都设置为 true,则可以执行任务。
显然,这会限制某些功能:每个任务都附加了某个操作(事务、计算、输入/输出活动等)。但是,此时,我们更关心任务是否被执行,而不是其具体含义。
一旦任务完成,输出变量向量将被设置为 true。
应该提到的是,输入和输出条件设定了任务之间的链接。上述模型允许构建一个自动求解器,该求解器能够在流程的每个阶段回答前面提出的两个问题:
- 哪些任务可以(或应该)完成?
- 还有哪些任务尚未完成?
模型实现
该系统是用 C# 实现的。选择这种语言的唯一原因是这是我工作场所当前的环境。也可以使用 Java、C/C++、DELPHI 等。解决方案附在本文章中。下面我将解释实现中的主要部分:
1. 任务
类 Task
对应于任务的概念。此类实现了接口 ITask
。此接口最重要的元素是:
- 方法
Done
;它模仿执行任务的过程。和
- 属性
IsDone
;它允许检查任务是否已完成。
类 Task
的构造函数的输入参数是:任务的标识符,输入(radarIsReady
、operatorIsReady
)和输出(detectionIsDone
)条件列表(Condition
对象将在第 2 页描述)。
ITask objectDetection
= new Task("ObjectDetection",
new Conditions(detectionIsDone),
new Conditions(radarIsReady,operatorIsReady))
;
一组 Task
类型的对象构成了系统。该系统通过具有 IEnvironment
接口的对象实现。
函数 Done
执行以下操作:
- 它将输出条件设置为 true;
- 它将属性
IsDone
设置为 true; - 它根据输出的变化修改系统(
Reset
);
2. 条件
如前所述,条件是一个普通的布尔变量。此处,变量由接口 ICondition
描述,并在类 Condition
中实现。用户只需使用构造函数创建此对象即可。
ICondition condition = new Condition();
3. 环境
Environment
是 Task
类型对象的集合。Environment
对象的主要元素是:
- 方法
Ready
,它提供了一个可运行任务的列表。 - 方法
Reset
,它刷新系统状态。用户通过Task
对象的操作Done
(第 1 页)间接调用此方法。
4. 主要操作
用户应执行三个主要操作:
- 描述系统。
- 运行任务。
- 检查系统状态。
最后两个步骤可以无限重复。
5. 系统描述
图 3 中所示的系统由以下过程描述:
ICondition
postConditionTask1 = new Condition();
ICondition
postConditionTask2 = new Condition();
ICondition
postConditionTask3 = new Condition();
ICondition
postConditionTask4 = new Condition()
;
ITask
Task1 = new Task("Task1",
new Conditions(postConditionTask1),new Conditions());
ITask
Task2 = new Task("Task2",
new Conditions(postConditionTask2),new Conditions());
ITask
Task3 = new Task("Task3",
new Conditions(postConditionTask3),new Conditions());
ITask
Task4 = new Task("Task4",
new Conditions(postConditionTask4),
new Conditions(postConditionTask1,postConditionTask2));
ITask
Task5 = new Task("Task5",
new Conditions(),
new Conditions (postConditionTask3,postConditionTask4));
IEnvironment
environment = new Environment(
new Tasks (Task1,Task2,Task3,Task4,Task5))
;
6) 运行任务
要运行任务,必须为特定的 Task
调用 Done
方法,例如:
Task1.Done(environment);
7) 系统状态验证
我们使用方法 IEnvironment::Ready
。
特别是,在创建系统并运行 Ready
过程后,可以检查任务 1 和任务 2(参见第 5 页)是否已准备好运行。
通过属性 IsDone
,可以检查是否没有任务被完成。
ITasks list = environment.Ready()
;
Console.WriteLine("Task1 is ready to execution : {0}",list.Contains(Task1));
Console.WriteLine("Task2 is ready to execution : {0}",list.Contains(Task2))
;
Console.WriteLine("Task1 is done : {0}",Task1.IsDone);
Console.WriteLine("Task2 is done : {0}",Task2.IsDone);
Console.WriteLine("Task3 is done : {0}",Task3.IsDone);
Console.WriteLine("Task4 is done : {0}",Task4.IsDone);
Console.WriteLine("Task5 is done : {0}",Task5.IsDone)
;
限制
如图 5 所示,禁止直接和间接循环。
注意:上述应用程序假设方案中不包含直接或间接循环。
测试应用程序
测试应用程序描述了图 6 所示的过程。
为了说明起见,每个按钮都对应一个特定的任务。任务的执行通过点击按钮来模拟。相邻的复选框指示任务的状态:已完成/未完成,参见图 1。
项目简述
随本文提供的解决方案包含 4 个项目:
- 项目 Environment 包含所有接口以及实现它们的类。这只是解决方案的核心。
- 项目 TestConsole 说明了创建项目方案和运行一些向导命令的最简单方法。
- 项目 LogicLayer 描述了图 6 中所示的图。
- 项目 TestWinApplication 是一个 GUI 应用程序。它说明了向导如何仅使用最少的图形工具来运行,参见图 1。
历史
该应用程序大约在一年前编写了几天。我认为它可能会引起广大读者的兴趣,因此决定提交这篇短文。