65.9K
CodeProject 正在变化。 阅读更多。
Home

工作量系统

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (7投票s)

2009年8月7日

CPOL

21分钟阅读

viewsIcon

41966

计算言语语言病理学家(SLP)的工作量是一个非常复杂的过程。必须开发一种系统方法来计算工作量并建立标准。

摘要

计算言语语言病理学家(SLP)的工作量是一个非常复杂的过程。必须开发一种系统方法来计算工作量并建立标准。为了满足学校环境中工作量计算的需求,将开发一个工作量分析系统。工作量分析系统允许学区计算言语语言服务的预算,并为言语语言病理学家分配适当的工作量。

背景

在学校环境中,言语语言病理学家(SLP)通常有大量的病例。计算工作量的整个过程非常复杂。本项目旨在系统地分析计算工作量的过程并建立一个标准。根据美国言语听力协会(2002)的立场声明,病例量(caseload)一词指的是学校SLP直接服务的学生数量。相比之下,工作量(workload)一词指的是SLP所需并执行的所有活动。病例量和工作量之间的差异使得制定计算工作量的标准非常困难。为了满足学校SLP工作量计算的需求,必须开发一个规则引擎系统来分析学校SLP的工作量。工作量计算的基本标准如下:

  1. 当一个学生(病人)被添加到病例量中时,SLP不仅要花费时间进行直接或间接的服务和评估,还要处理强制性的文书工作、多学科团队会议、家长和老师联系以及相关职责。
  2. 病例量的大小必须允许SLP提供适当和有效的干预。

学校必须实施工作量分析方法来设定病例量标准,以使SLP能够参与满足个体学生需求所需的广泛专业活动。

同时,为了计算个人工作量和预算,工作量分析系统是必要的。该系统可以根据每个学区的需求进行定制。此外,校长可以使用该系统计算言语语言服务的预算。通过使用工作量分析系统,学区可以确保他们的言语语言病理学家拥有合理的工作量,并且学生可以获得充分的服务。

引言

该系统将采用标准的三层架构和.NET 2.0框架进行设计和实现。基于逻辑的三层应用程序,我可以获得以下优势:将所有业务类封装并集中到一个易于创建、使用和重用的.NET程序集中。这有助于我更轻松地维护代码。数据库访问类被分离为独立的.NET组件,因此前端代码中没有嵌入SQL。三层架构还提供了分离组件以与其他组件集成的灵活性。这有助于提高可扩展性和更好地集中代码。(Sheriff, P. D., 2002)。.NET 2.0框架是Microsoft开发的.NET平台的编程模型。 .NET框架内部有两个主要组件:一个是公共语言运行时,另一个是.NET框架类库。.NET框架类库提供了丰富的程序集组件,包括ADO.NET、Windows窗体和其他DLL。 .NET框架是一个托管执行环境。它帮助开发人员简化开发和部署。它还集成了各种编程语言,如VB.NET、C++、C#...(Microsoft .NET Framework, 2006)。

在我的工作量系统中,第一层是表示层。在第一层中,Windows窗体可以用于接受用户的所有输入。在Windows窗体后面,VB.NET类将处理窗体事件调用,与业务对象(我的第二层)进行通信。我的所有业务对象都将位于第二层。在第二层中,规则引擎组件也可以被放置,与业务对象进行交互。第三层将包含我的所有数据访问类。数据访问层可以应用领域设计模式。参见图1。

Figure_1.JPG

工作量系统分析

一旦我对系统有了基本的想法,我就可以看到有几个主要问题需要我投入精力进行分析并为工作量系统找到解决方案。

业务对象

由于我的应用程序是基于面向对象概念设计的,业务对象将是我系统的核心组件。所有业务对象都将集中到一个单独的层中。

对于每个业务实体,都会创建一个.NET类对象来表示它。在每个对象类中,所有私有数据都将通过公共属性字段来处理,这些字段可以从类外部访问。还将定义一个接口(`IData`)。`IData`应包含所有数据库功能调用,例如插入、更新、删除和选择。一旦业务对象继承了`IData`接口,它将被强制提供数据库方法的实现。`IData`接口提供了业务对象与数据访问对象通信的能力。然而,业务对象不仅应包含数据,还应定义与该数据相关的所有业务功能。来自Windows Form UI的所有工作只能通过调用业务对象传输到数据库。

为了跟踪业务对象数据是否已更改,我将引入一个`DataChange`事件。只要Windows窗体中的数据发生变化,就会触发`DataChange`事件。一旦`DataChange`事件被触发,它将调用规则引擎开始验证业务对象。

正如我之前所说,我的大部分业务对象都与数据相关。UI可以与对象交互来操作数据。因此,整个系统将应用“负责对象”的概念。类本身应该知道如何创建自己的实例。例如,我有一个名为`SLP`(言语语言病理学家)的类。当主管点击新建SLP按钮以从Windows Form创建新的SLP时,我可以简单地使用`New`关键字来创建新的`SLP`对象。尽管主管尚未输入新SLP的任何信息,`SLP`类仍将默认创建一个SLP,该SLP在创建过程中即可使用。示例:

Dim oSLP as New SLP()

在对象的构造函数中,我编写代码通过使用`SetDefaultSLP()`方法将`SLP`实例与数据库关联起来,并将返回的主键设置回SLP `ID`属性。我实际上是通过`SLP`的主键将一个SLP数据库记录与一个`SLP`实例绑定起来。示例:

Public Sub New()
    Me.ID = SetDefaultSLP()
End Sub

由于SLP类实现了`IData`接口,`oSLP`将很容易接受所有的数据库Select、Update和Delete方法调用,以实现并更新自身到数据库。

我面临的一个重大问题是,在需要处理所有业务需求时,将关系数据库数据转换为对象模型,然后在业务处理完成后,再将其更改回关系模型SQL Express数据库。为了解决这个问题,我希望将对象模型和关系模型结合到业务类中。所有数据都将作为属性与对象一起存储。对象行为将通过方法实现。例如,患者对象具有Age、Type、Sex属性来保存数据。`SelectPatient (patientID)`是患者的行为。一旦调用`SelectPatient (patientID)`,它将调用一个存储过程,根据传入的`patientID`从数据库中检索所有相关信息。一个.NET `DataRow`对象将被返回。对于`DataRow`对象中的每个字段,将值赋给相应的属性。示例:

Dim oPaitient as New Patient
oPaitient.SelectPatient(patient123)

在`Patient`类中

Private _age as integer 
Public Property Age() As Integer
 Get
           return _age
 End Get
 Set(ByVal Value As Integer)
	 _age = value 
End Set 
End Property 
Public Sub SelectPatient(byval patientid as integer) 
	Dim dr as new datarow 
	dr = getPatient(patientid) 
	for each field in dr
                	Me.Age = dr(“Age”) 
		Me.Type = dr(“Type”) 
		......
	end for 
End Sub

尽管我在业务逻辑层中将所有业务对象定义为单独的类,但我仍然看到一些共同的字段可以在某些对象之间共享,我需要一次或多次将它们存储到数据库中。例如,`SLP`和`Supervisor`都是系统中的业务对象。它们都可能属于学校或康复中心的`Employee`类型。`Patient`(学生)是系统中的另一种业务对象类型。它们都以某种方式与人相关。作为一种人员类型,这三个类有一些共同的字段,例如人员的ID、姓名、出生日期、年龄……因此,`Person`基类可以用于为`SLP`、`Supervisor`和`Patient`类提供一些默认实现。在顶层,我将`Person`类定义为基类。`Person`类提供`ID`、`Name`、`DOB`、`Age`……以及具有默认实现的`CalculateAge`、`DisplayName`。`Person`类中的`CalculateAge`方法将根据人员的DOB分配`Age`属性。`Person`类中`DisplayName`存在多态行为。默认情况下,`DisplayName`只会连接人员的名字和姓氏,并将其作为全名返回。`Employee`类继承自`Person`类,并重写`DisplayName`方法以将职称连接为姓名的一部分。`SLP`类继承自`Employee`类,并通过添加员工ID而不是职称来重写`DisplayName`方法。`Patient`类也继承自`Person`类。`Patient`需要将房间号连接到姓名前面,以覆盖`Person`类中的默认`DisplayName`方法。示例:

<seriallizable()>
Public MustInherit Class Person 
	ID, DOB,AgeName…
   	Pubic Function CalculateAge() as integer 
  		Assign the Age base on the DOB 
	End
	Public Overridable Function DisplayName() as String
		Return firstname + lastname
	End
End Class

<serializable()>
Public Class Employee
	Inherits Person
		title 
		Public Overrides Function DisplayName() as String
			Returntitle + firstname + lastname 
		End 
End Class

<serializable>
Public Class Patient 
	Inherits Person
		roomnum
		Public Overrides Function DisplayName() as String
 			Returnroomnum + firstname + lastname
 		End  
End Class

<<serializable()>
PublicClass SLP 
	Inherits Employee
	Public Overrides Function DisplayName() as String 
		Returnemployeeid + firstname + lastname 
	End
End Class

<serializable()>
PublicClass Suspervisor
	Inherits Employee
 	....use DisplayName from employee class 
	Public Overrides Function DisplayName() as String 
		Return MyBase.DisplayName()
	End 
End Class

从上述结构中,我不仅利用了面向对象的继承概念,还利用了多态性特性,通过可选地重写基类中的`DisplayName`,应用于我的所有子类。

规则引擎组件

除了业务对象之外,规则引擎是工作量系统的另一个关键组件。工作量系统的目标之一是简化对违反业务规则的跟踪,并根据所分配患者的情况评估SLP的工作量。示例:

Business Rules

  • 一次工作量计算必须且只能有一个SLP
  • 一名SLP至少分配有一名患者
  • 工作量总分必须为正数
  • 出生日期必须是日期格式

基于患者状况的公式

  • 无直接服务-3
    • 学业支持需求 + 5
    • 仅轻度发音障碍-1

`WorkLoad`是为整个过程创建和利用的最大对象。`WorkLoad`对象包含其他对象,例如`SLP`、`Patient`……为了提供业务规则检查,每个`WorkLoad`对象都将有一个相关联的当前违反业务规则的集合列表。由于这是一个规则检查过程,只会返回一个布尔值来指示传入的`WorkLoad`对象是否满足业务要求。如果结果为false,则针对无效字段或要求的规则问题描述将存储在违反规则集合列表中并显示给用户。

为了根据公式计算工作量分数,我使用相同的方法。在`WorkLoad`对象中,有一个`totalWorkLoadPoint`属性,用于记录传入的工作量经规则引擎消化后的工作量分数。当分析如何将规则引擎组件与业务对象部分集成时,我将讨论规则引擎用于检查工作量对象的算法。

所有的工作量对象将反复使用相同的规则引擎来验证和评估规则和公式。因此,规则引擎组件可以是一个存在于整个工作量应用程序域中的静态进程。考虑到线程安全方面,我认为单例模式将满足要求。在规则引擎组件加载之初只会创建一个实例。在规则引擎类中,`GetRulesEngine`方法总是检查规则引擎实例是否已初始化。如果是,则使用现有的规则引擎实例,否则为规则引擎类创建一个新实例。示例:

Public Class RulesEngine 
	Private Shared singleRulesEngine As RulesEngine 
	Public Shared Function GetRulesEngine() As RulesEngine 
		If singleRulesEngine Is Nothing Then 
			singleRulesEngine = New RulesEngine()
			 Return singleRulesEngine
		Else 
			Return singleRulesEngine 
		End If 
	End Function 
End Class

在规则引擎组件中,可以声明规则集类来表示所有业务规则和公式。所有业务规则和公式将作为工作量系统安装过程的一部分,通过运行SQL脚本存储在SQL Express 2005数据库中。规则集类将在规则引擎首次被调用时加载所有规则和公式。每个规则记录可以表示为`RulesSet`类的一个独立实例。在规则引擎类中,一个集合字典在整个过程中缓存所有规则。

Public Class RulesSet 
	Private ID 
	Private FieldToValidate
	Private Formula 
	Private Condition
	Private PreCondition 
	Private Rules
	Private RuleDescription
	Private workLoadPoint
 End Class
Public Class RulesEngine 
	Public Shared Property RuleSetCollection() As Dictionary(Of Integer, RuleSet)
		 If RuleSetCollection Is Nothing then
			ReturnRuleSetCollection = LoadRuleSet 
		Else 
			Return RuleSetCollection 
		End if 
	End
End Class

在`RulesSet`类中,公式是最难声明和实现的部分。我可能会做的是将所有公式定义为正则表达式,这些正则表达式可以从.NET 2.0内置的正则表达式库中读取。正则表达式类中有一个`Match`方法,它可能有助于我将文本作为输入字符串,并对其进行处理以挑战所提供的表达式。

Imports System.Text.RegularExpressions
Public Class RulesEngine 
	Public Function Evaluate (ByVal rule.formula As String, _
	                          ByVal toEvaluate As String) As Boolean
		Dim mRegExp As Regex 
		Try mRegExp = New Regex(rule.formula)
			If mRegExp.IsMatch(toEvaluate) Then 
				Return True 
			Else 
				Return False 
			End If 
		Catch exp As Exception 
			Return	"Exception occured:" &  exp.ToString() 
		Finally
			 mRegExp = Nothing 
		End Try End Function
End Class

`Evaluate`方法将根据评估过程返回的结果完成所有业务规则和公式验证。如果业务失败,规则的描述将添加到工作量违反规则集合列表并返回给用户。如果公式通过,则`workLoadPoint`将添加到传入工作量对象的`totalWorkLoadpoint`中。

将规则引擎组件与业务对象集成

当我的业务对象和规则引擎准备就绪后,下一个挑战是如何让它们相互协作。首先我想指出的是,所有的规则和公式都将只应用于对象属性。这就是我之前在`RulesSet`分析中定义`FieldToValidate`的原因。`FieldToValidate`是我在运行时业务对象中实际需要验证的字段,因此`FieldToValidate`中的描述需要遵循业务类的相同结构。例如,在我的一个`WorkLoad`对象中,我有一个关联的SLP。在`SLP`对象中,有一个`HiredDate`字段,根据我的业务规则定义列表(参见图3),它必须是`Date`类型。

Figure_3.JPG

图 3

`FieldToValidate`需要反映相应的业务对象属性,因此我将在`HireDate`业务规则的`FieldToValidate`中放置以下结构(参见图4)。

Figure_4.JPG

图 4

这样做的好处是,通过`FieldToValidate`的内容,我可以轻松地找出规则引擎需要根据特定业务规则评估哪个属性。一旦我知道了字段,那么我认为.NET Framework中的`Reflection`类可以帮助我在需要验证业务规则时在运行时检索当前对象的属性值。我从运行时对象中检索到的值和公式可以作为正则表达式字符串(输入字符串)和正则表达式(规则)传递给`Evaluate`进行验证。以下算法分析了规则引擎调用评估工作量对象时的工作流程。它可能帮助我们理解整个评估过程。

  1. 接收一个`WorkLoad`对象。
  2. 根据需要验证的字段提取当前的`WorkLoad`对象。所有未检查的业务规则字段将被移除。
  3. 将提取的`WorkLoad`传递给规则引擎。
  4. 规则引擎在第一次调用时加载所有规则并将其缓存到`RuleSetCollection`(一个字典列表)中。
  5. 遍历每条规则。
  6. 对于每条规则
    • 6.1 根据`FieldToValidate`内容检索值。
    • 6.2 使用公式表达式评估检索到的值。
    • 6.3 检查前置条件(如果不为真则退出)。
    • 6.4 如果前置条件为真,则检查公式。
      • 6.4.1 对于规则类型:如果结果为false,则将规则添加到违反规则列表。
      • 6.4.2 对于公式类型:如果结果为true,则添加`totalWorkloadPoint`。
  7. 对于日期,`WorkLoad`违规规则列表和`totalWorkLoadPoint`。

每个规则都应有两个与之相关联的条件:条件和前置条件。条件是需要直接验证的主要规则。前置条件是与该规则相关联的预先要求。只有在前置条件被验证后,条件才能开始验证。例如:患者需要**学业支持**。该规则的前置条件是患者是学生。因此,规则引擎将加载`patienttype`来检查该患者是否是学生。因此,只有当`patienttype`是`student`时,才会检查“学业支持需求”规则。如果一个规则需要多个前置条件,那么正则表达式提供了一种将两个前置条件连接成一个前置条件的方法。所有规则都非常直观,并且与所有业务对象属性相关联。因此,所有条件和前置条件将仅依赖于对象的属性检查并返回一个布尔值。没有规则依赖于其他规则;每个规则都应清楚地说明一个特定条件,目前在系统的设计阶段不考虑规则交互场景。在集成规则引擎和业务之后,无论字段何时更改或更新,业务对象也可以立即更新违反规则列表和`totalWorkLoadPoint`。由于所有规则都将在安装开始时通过运行数据库脚本手动创建,因此所有规则都应存在于数据库中,以便用户目前无法修改它们。如果规则更新场景发生更频繁,我可能会创建另一个Windows窗体,允许用户直接从工具维护和更新所有规则。

通过数据访问层传输业务层数据

数据库层就像一个桥接层,用于将我的所有业务对象保存到关系数据库中。幸运的是,ADO.NET已经提供了一套类,可以向.NET程序员公开数据访问服务。我只需要通过调用存储过程来开发所有数据活动,这很简单。当业务类实现IData接口时,它将为该类提供数据库方法调用。面向对象编程的一大优势是将所有功能封装成一个程序集,并与其他项目进行插件式集成。为了节省我的工作,我将使用Microsoft DataApplicationBlocks。根据Microsoft官方网站的说法,“数据访问应用程序块是一个.NET组件,包含优化的数据访问代码,可帮助您调用存储过程并对SQL Server数据库发出SQL文本命令”(Microsoft,2006)。通过借用现有的数据应用程序块,我无疑为本项目中的所有数据访问工作节省了大量时间。我只需要创建一个简单的类,该类具有基本的数据库信息,即可将我的业务逻辑与Microsoft DataApplicationBlocks连接起来。从我自己的数据访问类中,我只需要为我的数据库活动提供一个事务调用。在.NET 2.0中,还引入了事务范围来处理多个事务。因此,我只需要在我自己的数据访问类中创建一个事务范围来监控我的所有数据访问工作。为了使应用程序更具可配置性,我决定将数据库连接字符串放在配置文件中,该文件是基于xml格式的。用户可以轻松修改连接字符串以指向不同的数据库。据我所知,数据库对象是非托管组件,垃圾回收器不会清理它们。为了防止内存泄漏问题,我必须手动正确处置我的数据访问对象。我可以使用的方法是为我的数据访问对象实现IDisposable接口,以便在我的数据访问对象需要销毁时处置所有连接。在数据访问层中,可以应用对象关系行为设计模式(Fowler,M. 2007)的身份映射,以仅允许每个对象实体加载一次。因此,ID属性在业务实体类中对于跟踪每个唯一记录非常有帮助。它应该有助于提高性能,因为对象实体只加载一次并缓存以供反复重用。

工作量系统设计

为了方便用户,我决定创建一个工作量系统作为Windows应用程序,可以轻松地安装在用户的PC上。工作量系统中存在几个用例(参见图-5)。

figure_5.JPG

图-5

用例

创建SLP(患者,工作量)

用户点击新建SLP按钮,调用SLP子窗体。用户在SLP子窗体上输入所有SLP的信息。点击保存按钮将SLP保存到数据库。(参见图-6)

figure_6.JPG

图-6

编辑患者(SLP,工作量)

用户从窗口窗体数据网格中查看所有患者。用户选择一名患者开始编辑其信息。(参见图-7)

figure_7.JPG

图-7

创建工作量并扩展业务规则验证

用户创建新的工作量并为其分配一个SLP。在工作量表单中,用户可以选择分配给SLP的患者数量。工作量创建后,它将传递给规则引擎对象开始验证。规则引擎将结果返回给工作量表单,并在其中显示给用户进行审核。(参见图-8)

figure_8.JPG

图-8

类图

业务类图(图-9)展示了所有业务类。目前我只关注数据库实体。日志和异常类也可以从Microsoft应用程序块中借用。

figure_9.JPG

图 - 9

规则引擎类(图 – 10)图显示了所有规则引擎组件类。

figure_10.JPG

图 - 10

数据库结构

数据库实体关系图(图-11)说明了工作量系统所需的所有关系数据表。

figure_11.JPG

质量属性场景

为了实现能够支持工作量系统架构的质量属性目标。质量属性矩阵描述了与所有质量属性相关的策略和方法。

目标

如何实现

所用策略

可用性

工作量系统是一个 Windows .NET 应用程序。该应用程序将直接安装在客户端机器上,因此它作为单个应用程序运行。可用性没有限制。不需要互联网访问。数据库也将安装在同一台机器上。

异常,事务

可修改性

该应用程序也可以使用 ClickOnce 技术部署。每当工作量系统的新版本发布到服务器上时,客户端都会收到有关新更新的通知。

Windows ClickOnce 更新服务。

安全

可以使用身份验证来允许有效用户使用应用程序。并且可以从配置中配置授权,以限制用户访问本地数据库。审计表跟踪所有活动。

授权用户、限制访问和审计跟踪。

容量

由于它是Windows应用程序,所以系统主要适用于单用户。

规则引擎采用单例模式

可用性

窗口将是唯一的界面,大多数用户已经熟悉。简单的按钮点击提供了大部分功能。每个活动都将调用子窗体。

分离用户界面,大多数窗体都存在取消、保存功能

可负担性

.Net 框架和 SQL Express 数据库都是免费的,可以从 Microsoft 网站下载。客户无需支付任何费用。

仅进行成本分析和开发

结论

基于上述分析和设计,我已经非常详细地描述了工作量系统的信息。使用设计模式来支持工作量系统架构也有一些优点。由于我也获得了.NET 2.0框架的知识,我非常乐意使用.NET技术和SQL Server 2005来实现。

用户评论

本系统中使用的规则已经过言语语言病理学家(我的妻子,她将在学校系统工作)以及她的主管的审核。言语语言病理学家表示,这些规则是有效的,该系统对于学校的言语语言病理学家计算工作量将非常有帮助。该系统将作为一个工具实施,未来将分发给学校的言语语言病理学家进行审查和测试。

参考文献

© . All rights reserved.