C# 中的业务对象简介






4.54/5 (20投票s)
2002年3月12日
11分钟阅读

244783

2805
使用简单的 Person 对象作为示例,
注意:下载后请务必先查阅 ReadMe.txt。
引言
本示例是对 Rockford Lhotka 的组件化可伸缩逻辑架构(CSLA)的介绍,该架构在其著作《Visual Basic 6 业务对象》(Wrox Press,1998)中有详细描述。CSLA 是 n 层设计的实现,其中应用程序在逻辑上划分为四个层:表示层、以 UI 为中心的业务对象、以数据为中心的业务对象和数据服务。该架构的目的是实现两个首要目标。
- 可伸缩性。
- 各层在物理位置上的灵活性。
后者意味着我们可以决定将所有层放在一台机器上,还是将其分发到客户端和服务器机器之间,而对业务逻辑代码的影响很小或没有影响。这也意味着我们可以提供各种表示层,从传统的 Windows 界面到 Web 界面,而对业务逻辑代码的影响很小或没有影响。
典型的物理架构,采用丰富的 Windows 风格用户界面,是将以 UI 为中心的业务对象放置在客户端工作站上,使其靠近用户界面。以 UI 为中心的业务对象与以数据为中心的业务对象通信,后者放置在应用程序服务器上,而后者又与数据服务通信,数据服务位于数据库服务器上。
此处以 C# 形式呈现的代码并非该架构的完整实现。相反,它旨在说明基本概念。它提供了一个以 UI 为中心的业务对象,用于捕获关于一个人的基本信息:社会安全号码、姓名和出生日期。它展示了 Microsoft .Net Windows 窗体用户界面如何与业务对象交互,同时允许业务对象在用户输入数据时验证所有业务规则。用户界面完全隔离了详细的业务规则验证。它只需要对业务对象的属性做出响应——无论它当前是处于有效还是无效状态。
UML 类图Person 类
Person 类具有三个已提供的属性:
- 社会安全号码
- 名称
- 出生日期
以及一个计算属性。
- 年龄
它有三条业务规则:
- 社会安全号码包含正好 11 个字母数字字符(尽管示例要求用户输入数字)。
- 姓名不能为空。
- 出生日期必须格式有效。
在这些规则得到满足之前,Person 对象不被视为处于有效状态。
为了提供最丰富、响应最快的用户体验,我们希望在用户输入数据时验证这些规则,而不是在所有数据输入完毕后再进行验证。其他用户界面,如 HTML 3.2 界面,可以满足于“批量”风格的界面,但我们希望能够使用各种用户界面,而 Windows 风格的 GUI 提供最丰富的体验。
基本思想是,仅当用户提供了使 Person 对象有效的必要数据时,用于保存用户数据的控件才应该可用。用户界面开发人员的工作是编写代码来实现这一点。但他们不需要编写详细的验证代码,只需要查询 Person 的“Valid”属性。
然而,实际上,UI 开发人员需要比这更多地了解 Person。但他们需要了解的与业务规则无关,而是与 Person 在任何时候的操作状态有关。
让我们考虑可以对 Person 执行的操作:
添加一个新的 Person。
将其保存到数据库。
从数据库加载一个现有的 Person。
删除一个 Person。
一个丰富的用户界面将具备以下功能,使用户能够:
编辑其数据,保存更改并关闭应用程序。
编辑其数据,保存更改,进行进一步编辑,保存并关闭。
编辑其数据,取消更改并关闭。
依此类推。
在数据录入窗体中,上述通常通过“确定”、“取消”和“应用”按钮来实现。这可能看起来并不特别麻烦。然而,该架构的一个特点是 Person 对象的状态在用户输入时会被更新。如果用户取消编辑,则必须将对象恢复到编辑之前的最后一个有效状态。这意味着我们必须在每次“应用”操作后保留当前状态的副本。
在这个特定的例子中,这并不重要,但在更复杂的应用程序中,例如包含 Person 集合的应用程序中,就会很重要。我们正试图说明通用原理。
UI 开发人员还需要了解 Person 的其他一些信息,例如 Person 是新创建的、已修改的(脏数据)还是当前正在编辑中。
例如,在编辑现有 Person 时,不应该能够从数据库加载新 Person。
管理编辑
为了方便编辑,提供了三个方法:
BeginEdit
启用编辑。
ApplyEdit
根据需要保存或删除对象并终止编辑。如果客户端希望继续编辑,应立即调用 BeginEdit。
CancelEdit
取消自上一次 ApplyEdit 操作或自对象被标记为删除以来发生的所有更改。终止编辑。
在数据库术语中,这三个方法分别类似于“BeginTrans”、“Commit”和“Rollback”,但应用于对象而不是数据库。
管理业务规则
在 Rockford Lhotka 最初的 Visual Basic 实现中,他创建了一个 BrokenRules 类,该类可供应用程序中的每个业务对象使用。在本示例中,我将此功能移至一个抽象基类 BusinessObject。我也可以在这里放置一个抽象接口来处理三个编辑方法,并将实现推迟给 Person,但我选择不这样做。
Person 对象的有效性或无效性是通过维护一个已损坏业务规则的集合来确定的。当集合非空时,对象无效。当计数变为零时,对象变有效。
维护集合的方法是:
void RuleBroken(string rule, bool isBroken)
其中
rule
是对业务规则的描述。这可以像属性名称一样简单,例如“Birth Date”。
isBroken
指示规则是否已损坏。
当规则损坏时,将其添加到集合中。如果规则反复损坏,算法将跳过添加。当规则不再损坏时,将其从集合中移除。
当 Person 对象首次构造时,其所有字段都将为空,因此所有规则都将损坏。记住,规则是:
- 社会安全号码包含正好 11 个字母数字字符。
- 姓名不能为空。
- 出生日期必须格式有效。
在 Person 构造函数中,我们编写:
RuleBroken("Social Security Number", true);
RuleBroken("Name", true);
RuleBroken("Birth Date", true);
当然,这通常不是我们应该构造对象的方式。通常对象应该始终处于有效状态。然而,这里讨论的对象是一种特殊类型。当对其执行任何重要操作时,例如持久化或恢复它,该方法会确保对象在持久化之前或恢复之后是有效的。
使 Person 有效
其思想是,Person 对象应随着用户输入数据而从无效状态变为有效状态。最初,每个受业务规则约束的项都将是错误的。然后,用户逐个完成数据输入字段,每个字段应变有效,直到 Person 对象变有效。
这是通过适当构造的 Person set_xxx 属性和 Form TextChanged 事件实现的。
因此,对于社会安全号码,我们有:
在 `Person.set_SocialSecurityNumber` 属性中。(为简化说明)
set
{
socialSecurityNumber = value;
RuleBroken("Social Security Number", socialSecurityNumber.Length != 11);
}
在 `Form.txtSocialSecurityNumber_TextChanged` 事件中。
person.SocialSecurityNumber = txtSocialSecurityNumber.Text;
TextChanged 事件在每次按键时发生,并且每次按键时,Person 的 set_SocialSecurityNumber 属性都会被调用,它会调用 RuleBroken。
注意表达式:`socialSecurityNumber.Length != 11`。
这描述了使规则“损坏”的条件,即“true”。
第一次按键时,社会安全号码的长度不等于 11,因此它会被添加到已损坏规则的集合中。在后续的按键中,它仍然被视为已损坏,因此 `RuleBroken` 将跳过将其添加到集合中。但当键入第 11 个字符时,`RuleBroken` 收到其参数的 false 值,表示规则不再损坏,因此将其从集合中移除。
此过程对姓名和出生日期属性重复进行,直到 Person 对象变有效。
告知客户端 Person 是否有效或无效
Person 类公开了一个 IsValid 属性,但也公开了 Valid 和 Invalid 自定义事件。这两个事件由客户端(在此例中为 Windows 窗体应用程序)处理,以启用或禁用 Person 对象的保存。这意味着启用或禁用“确定”和“应用”按钮。
要触发事件,Person 类(实际上是基类)会声明一个事件处理程序委托和两个事件。
public delegate void EventHandler(object sender, EventArgs e);
public event EventHandler OnInvalid;
public event EventHandler OnValid;
要触发 `OnValid` 事件:
if (OnValid != null)
{
OnValid (this, EventArgs.Empty);
}
当已损坏业务规则的数量降至零时,`RuleBroken` 会执行此代码。
客户端按如下方式处理事件:
// Subscribe to Person event
person.OnValid += new Person.EventHandler(Person_OnValid);
private void Person_OnValid(object sender, System.EventArgs e)
{
btnOK.Enabled = true;
btnApply.Enabled = true;
}
与实际实现相比,此事件处理程序代码已简化。但它说明了总体思路。
`OnInvalid` 事件的实现类似。
Age 属性
Age 属性很有趣。它被公开为只读的,因此客户端无法设置它。相反,它是根据出生日期计算的。但是,它可以作为动态属性公开给用户界面,该属性会在用户输入不同的出生日期时更新。
(注意:示例应用程序期望的日期格式为英国格式——日/月/年(dd/mm/yyyy)。)
假设用户输入的出生日期是 11/1/1969。这会导致年龄为 33 岁。其思想是,在用户输入 1969 的 9 的那一刻,将 33 显示在只读字段中。如果他们犯了个错误并决定更改年份,例如改为 1959 年,年龄字段应立即更新为显示 43 岁。可以通过在 Person 类中定义一个事件来实现这种行为:
public event EventHandler OnNewAge;
在 Person 对象中,一旦出生日期有效,就会引发此事件,并在每次它再次变为有效时重新引发,前提是新年龄与之前有效的年龄不同。
客户端按如下方式处理事件:
private void Person_OnNewAge(object sender, System.EventArgs e)
{
// Update the displayed age
lblAge.Text = person.Age.ToString();
}
持久化
持久化是通过一个“管理器”对象 PersonManager 实现的。它具有 Load、Save 和 Delete 方法,并与 Person 对象通信。数据访问通过 ADO.NET 和 Microsoft Access 数据库进行。
两者之间的接口非常简单。在生产环境中,我们需要使用 .Net Remoting 和序列化,因为 PersonManager 类通常会驻留在服务器上。
此外,在其书中稍后,Lhotka 简化了他的概念,并使用 Person 和 PersonPersist 对象来指代业务对象的以 UI 为中心和以数据为中心的半部分。
示例
如前所述,本示例采用了 Windows 窗体用户界面。它无意在用户界面设计方面赢得任何奖项。其目的是说明上述主要概念。
编辑新 Person
下面的屏幕截图显示了一个未完全编辑的 Person。出生年份是两位数格式,但需要四位数,因此“确定”和“应用”按钮被禁用。
一旦错误得到纠正,Person 对象就变得有效,“确定”和“应用”按钮就会被启用。请注意,年龄字段现在也已填充。
加载新 Person
Person 是通过其社会安全号码加载的。下面的屏幕截图显示了一个不完整的社会安全号码。它需要 11 位数字,因此“加载”按钮被禁用。姓名和出生日期字段也已禁用,因为我们不在编辑模式下。
添加第 11 位数字后,“加载”按钮将被启用。
Person 加载后,社会安全号码字段本身将被禁用。
但是,如果我们现在取消选中以启用编辑的复选框,所有字段都将被启用,并且此时我们也可以删除 Person,因为它不再是新的。
ASP.NET 客户端
我没有包含代码,但为了说明起见,下面的屏幕截图显示了 ASP.NET Web Forms 的实现。这演示了一个“批量”风格的用户界面。验证不会在所有字段都已输入并且用户单击“保存”按钮之一之前进行。而不是在 TextChanged 事件中进行验证,而是使用了 ASP.NET 的必需字段和自定义验证器。当单击提交按钮(即“保存”按钮之一)时,将调用这些验证器。第一个屏幕截图显示了无效的日期和错误消息。这在第二个屏幕截图中得到了纠正。
由于 Web 应用程序的无状态性,UI 的实现有点棘手。例如,在提交命令之后,窗体会被重新加载,这会使当前状态失效;Loading 复选框必须具有 AutoPostback=true 等等。但是,关键在于 Person 对象不需要进行任何更改。