分层编码 - 第二部分






4.67/5 (18投票s)
2003 年 9 月 25 日
7分钟阅读

83991

1759
在 Windows 和 Web 窗体中重用代码。
引言
本系列文章的第一部分(Part I)演示了如何使用 Automator
类自动填充 Windows Forms;但仅仅触及了将不同代码分离到多个层(tiers)的概念。在本文中,我们将详细介绍一个“账户注册”应用程序,该应用程序将通过我之前文章中描述的各种层的概念来演示代码的重用。
注意:本文提供的代码片段是实际代码的算法骨架,以降低文章的复杂度。读者应参考提供的源代码以获取更多详细信息。提供的源代码是经过充分注释且自解释的。
提供的可下载源代码包含了示例应用程序。我使用 Visual Studio .NET 2003 和 Framework SDK 1.1 来创建这个示例。
示例应用程序需求
本次练习的最终目标是通过一个以 Windows Form 或 ASP.NET Web Form 形式呈现的用户界面来捕获和存储注册信息。注册信息包含以下字段...
用户名
- 用于登录系统的账户用户名(必填)。密码
- 与用户名关联的密码(必填)。街道
- 街道地址。国家
- 居住国家(必填)。
用户将在表示层提供的字段中输入必要信息,然后单击“注册”按钮来请求创建账户。标记为必填的字段是强制性的,必须填写。输入的用户名必须是唯一的。如果输入的用户名已存在,则会通过显示适当的错误消息通知用户。如果不满足上述条件,则拒绝新账户的请求,并通过用户界面报告错误。如果所有必需的条件都满足,则输入的信息将存储在持久数据存储中,例如关系型数据库(RDBMS)。成功注册到系统后,用户将收到通知。
定义层
让我们通过分析功能需求,开始对应用程序要定义的各个层进行概念化。Windows Form UI 作为 Windows 应用程序(Win 项目)创建,而 Web Form UI 作为 ASP.NET Web 应用程序(Web 项目)创建。执行业务逻辑的代码应提取到一个公共库(Lib 项目)中。
辅助类
我在上一篇文章中使用的 Automator
类使用反射来自动填充窗体控件。由于 automator 特定于要填充的 UI,因此我们需要为 Windows 和 Web 表单创建单独的 automator 模块。
// Utility Classes
namespace Utility
{
public interface IState
{
void Reset(); // Method declaration to reset state
}
public interface IAutomator
{
void Store(); // Method declaration to store UI state
void Restore(); // Method declaration to restore UI state
}
public abstract class AbstractAutomator : IAutomator
{
private IState state; // UI State
public IState State
{
get {return this.state;}
}
}
}
// Presentation Tier (Windows)
namespace Win
{
// Automator for auto-populating Windows Forms
public sealed class WinAutomator : AbstractAutomator
{
public override void Store(); // Override to store UI state
public override void Restore(); // Override to restore UI state
}
}
// Presentation Tier (Web)
namespace Web
{
// Automator for auto-populating ASP.NET Web Forms
public sealed class WebAutomator : AbstractAutomator
{
public override void Store(); // Override to store UI state
public override void Restore(); // Override to restore UI state
}
}
我们将创建帮助类,用于通过反射获取或设置对象属性的值,因为此代码将由上述 Automator 共享。为此目的使用了两个类,即 ReflectHelper
和 PropertyHelper
。
public sealed class ReflectHelper
{
public static void GetProperty(object obj, string name)
{ // Uses Property Helper GetProperty(object, string) }
public static void SetProperty(object obj, string name, object val)
{ // Uses Property Helper GetProperty(object, string, object) }
public static void Populate(object obj, IDictionary dict)
{ // Populate obj using name-value pairs in the dict }
public static void Extract(object obj, IDictionary dict)
{ // Populate dict with properties on the obj }
}
public sealed class PropertyHelper
{
public static void GetProperty(object obj, string name)
{ // Return value of the named property on the obj }
public static void SetProperty(object obj, string name, object val)
{ // Set value of the named property on the obj }
}
PropertyHelper
扩展了反射技术,以用于嵌套属性。例如,考虑 Registration
类与 Address
关联的情况。下面的代码片段重点介绍了一些接口及其对应的实现(值对象),这些接口和实现用于存储 UI 的状态。
// Value Object interfaces
namespace Services
{
// Address data
public interface IAddress { // Declares Street property }
// User account data
public interface IAccount { // Declares Username property }
// Registration data
public interface IRegistration : IAccount
{ // Declares Addr property }
}
// Common Library
namespace Lib
{
public class Address : IAddress { // Implememts Street property }
public class Account : IAccount, IState
{ // Implements Username property }
public class Registration : Account, IRegistration
{ // Implements Address property }
}
假设 Registration
类的实例已分配给变量 reg。UI 控件可以通过使用如下所示的命名约定映射到 reg 的属性。嵌套属性用下划线“_”字符分隔,因为不允许使用句点“.”来命名 UI 控件。
Registration reg = new Registration();
控件名称/ID | 使用 reg 作为引用的属性映射 |
用户名 | 用户名 |
密码 | 密码 |
Addr_Street | Addr.Street |
Addr_Country | Addr.Country |
ReflectHelper.SetProperty(reg, "Username", "Popeye");
// sets Registration.Username to Popeye.
ReflectHelper.GetProperty(reg, "Username");
// gets Registration.Username returning Popeye.
ReflectHelper.SetProperty(reg, "Addr_Country", "India");
// sets Registration.Addr.Country to India.
ReflectHelper.GetProperty(reg, "Addr_Country");
// gets Registration.Addr.Country returning India.
ValidationHelper
和 ValidationException
实用类提供用于验证用户输入的方法。
public sealed class ValidationHelper
{
public static void ValidateRequired(string str)
{ // Validate str for non-null, non-empty content }
}
客户端层和表示层
表示层组件与客户端层密切相关,这表明表示层将包含两个单独的代码库,即 Windows 应用程序(Windows Forms)和 Web 应用程序(ASP.NET Web Forms)。
Windows Form 使用 System.Windows.Forms.dll 中的控件创建。使用此 DLL 的所有代码都归入 Win
命名空间。这包括 WinAutomator
类。
// Windows Application
using System.Windows.Forms; // Windows Forms
// Presentation Tier (Windows)
namespace Win
{
// Windows Form
public class WinForm : System.Windows.Forms.Form
{
// Windows Automator declaration
public WinForm() { // Create an instance of the windows automator }
// Register the user
private void RegisterButton_Click(object sender, System.EventArgs e)
{
// Store the current UI state using the automator
try
{ // Register the user by using the automator
// state with the registration service }
catch(ValidationException x)
{
ProcessErrors(this, x.Errors);
// Reset the automator state
}
}
private void ProcessErrors(Control control, IDictionary errors)
{
// Is the control name set?
if(control.Name != null)
{
// Is the control associated with an error message
if(errors != null && errors.Contains(control.Name))
{ // Display the error message }
else
{ // Reset the error message }
// Iterate over the nested controls in the collection
}
}
}
}
ASP.NET Web Form 使用 System.Web.dll 中的控件创建。使用此 DLL 的所有代码都归入 Web 命名空间。这包括 ErrorProvider
和 WebAutomator
类。
// ASP.NET Web Application
using System.Web.UI; // Web Forms
// Presentation Tier (Web)
namespace Web
{
// ASP.NET Web Form
public class WebForm : System.Web.UI.Page
{
// Error Provider for Web Forms creation
// Web Automator declaration
public WebForm()
{
// Set an ID for the form otherwise the
// auto population will fail.
this.ID = "WebForm";
// Create an instance of the web automator
}
// Members
// Register the user
private void RegisterButton_Click(object sender, System.EventArgs e)
{
// Store the current UI state using the automator
try { // Register the user by using the
// automator state with the registration service }
catch(ValidationException x)
{
ProcessErrors(this, x.Errors);
// Reset the automator state
}
}
private void ProcessErrors(Control control, IDictionary errors)
{
// Is the control ID set?
if(control.ID != null)
{
// Is the control associated with an error message
if(errors != null && errors.Contains(control.ID))
{ // Display the error message }
else { // Reset the error message }
// Iterate over the nested controls in the collection
}
}
}
}
请注意以上两种情况代码的显著相似性。程序员现在可以为任一表示库编写一致的代码;这得益于通用库和多层策略方法。
服务层
这里需要提出的问题是“应用程序需要提供哪些服务?”以下代码片段将定义应用程序要提供的服务的接口。
// Services Tier
namespace Services
{
// Register a user by creating an account
public interface IRegistrationService
{ void Register(IRegistration registration); }
// Authenticate the user into the system.
// This service is not currently used.
public interface IAuthenticationService
{ void Authenticate(IAccount account); }
// Service factory
public interface IServiceFactory
{
// Declare the IAuthenticationService property
// Declare the IRegistrationService property
public IRegistrationService RegistrationService
{
get;
}
}
}
namespace Lib
{
// A singleton factory to create and
// return the services provided
// Implements AuthenticationService and
// RegistrationService property
public sealed class ServiceFactory : IServiceFactory
{
private AccountManager acctManager =
new AccountManager(); // The Lib.AccountManager
public static IServiceFactory GetInstance()
{ // Return the singleton instance }
// Implement the IAuthenticationService property
// Implement the IRegistrationService property
public IRegistrationService RegistrationService
{
get { return acctManager; }
}
}
}
要提供的服务由业务层中的一个或一组类实现,在我们的示例中是 Lib.AccountManager
。表示层将通过使用这里定义的那些服务来通过服务层与应用程序通信。
业务层
此层包含执行业务验证和事务的类。它们在必要时与集成层的类通信,以执行业务逻辑并持久化由 UI 捕获的数据。
// Business Tier
namespace Lib
{
// An implementation of the services provided
public sealed class AccountManager :
IRegistrationService, IAuthenticationService
{
public void Authenticate(IAccount acct)
{ // Implementation of the authentication service }
public void Register(IRegistration registration)
{ // Implementation of the registration service }
}
}
集成层
访问后端资源(如关系型数据库)的类构成了此层的形成。本示例提供了如下代码片段所示的存根,以使用内存中的 Hashtable
来模拟实际行为,而不是实际的数据库。这些类与资源层通信以持久化或检索数据。
// Integration (Data Access) Tier
namespace Dao
{
// An abstraction of the Data Access Object
public abstract class AbstractDao
{ // Property to access the databse }
// Access to Account information
public class AccountDao : AbstractDao
{
public bool AccountExists(string username)
{ // Check if an account with the specified
// username already exists }
}
// Access to Registration information
public class RegistrationDao : AccountDao
{
public void Register(IRegistration registration)
{ // Register the user by persisting the data }
}
}
资源层
任何可以持久化数据的资源,例如关系型数据库或数据存储(如 Microsoft SQL Server 或 Oracle),都构成了此层的组成部分。此层中存储的数据由业务层通过集成层使用,以执行必要的业务逻辑。
摘要
上述各层可总结如下。
- 客户端层 - Windows 应用程序和 Web 应用程序。
- 表示层 - Windows Forms(
Win
命名空间)和 ASP.NET Web Forms(Web
命名空间)。 - 服务层 -
Services
命名空间,包含业务层中值对象的接口以及应用程序提供的服务。 - 业务层 -
Lib
命名空间,包含AccountManager
(提供服务层中上述服务的实现)以及Account
、Registration
等值对象(封装 UI 状态)。 - 集成层 -
Dao
命名空间,包含与资源层通信的数据访问对象。提供了此类对象的存根。 - 资源层 - 任何数据源,如 Microsoft SQL Server。本文未将其用于避免复杂性。
- 实用类 -
Utility
命名空间,包含抽象的Automator
和辅助类。
测试应用程序
源分发包含四个独立的项目,依赖关系如下表所示。
Utility
- 实用类库(Utility 命名空间)。Lib
- 服务接口、业务逻辑和数据访问库(Services、Lib 和 Dao 命名空间)。Win
- Windows 应用程序(Win 命名空间)。Web
- ASP.NET Web 应用程序(Web 命名空间)。
创建一个 Visual Studio.NET 解决方案,并将上述项目添加到解决方案中。
项目名称 | 依赖项 |
公用事业 | |
Lib | 公用事业 |
Win | Lib、Utility |
Web | Lib、Utility |
为运行 ASP.NET Web 应用程序创建一个名为 Web 的虚拟目录。在浏览器地址栏中输入 https:///Web/WebForm.aspx 来启动 Web 应用程序。可以通过位于 Win\bin\Release 目录中的 Win.exe 文件启动 Windows 应用程序。使用下面提到的步骤来测试应用程序(ASP.NET Web 或 Windows)。
- 不填写任何信息点击“注册”按钮 - 必填字段的验证错误。
- 填写所有必填信息并点击“注册”按钮 - 注册成功。
- 在不结束会话的情况下填写与上面相同的信息并点击“注册”按钮 - 重复账户的验证错误。
未来的可能增强功能
ASP.NET Web Forms 的 ErrorProvider
可以像 Windows Forms 的一样,通过继承 System.Web.UI.Control 的自定义控件来重写,从而在两种表示环境中提供一致的行为。
可以通过反射来填充集成层中的对象,方法是将属性名(包括嵌套属性)映射到数据库表中的列名。资源层中的通用功能可以提取到一个可重用的框架库中。
ValidationHelper
可以被一个验证框架替换,该框架可以在应用程序边界之间重用。
UI 表单的自动填充也可以通过自定义属性来实现,正如 Sébastien Lorion 在我上一篇文章的回复中建议的那样。
ASP.NET Web 服务可以像我们开发的客户端一样,通过 Windows 或 Web 客户端提供应用程序的功能。
参考/链接
- 使用 Microsoft .NET 构建企业解决方案模式 - Microsoft