组件、方面和动态装饰器





0/5 (0投票)
利用应用程序开发中的动态装饰器扩展组件功能并解决横切关注点。
引言
面向组件的开发已成为软件开发的标准方法。其优势包括简化、并行工作、可插拔的维护和重用,带来的经济效益是巨大的。组件也被认为是面向服务架构的启动平台的一部分,并在分层架构中发挥着关键作用。
组件通过只向外界暴露接口(服务契约)来隔离自身。组件的客户端在不知道或不在乎组件内部实现的情况下,使用组件暴露的接口。这在分离业务逻辑(功能需求)的关注点方面是件好事。然而,还有另一类关注点会跨越多个组件,甚至可能跨越整个应用程序。这些就是横切关注点(非功能需求),例如身份验证、授权、日志记录和仪表化。组件化开发本身并不能解决横切关注点。它需要借助 AOP 等其他工具来解决这些关注点。
在本文中,我将从应用程序开发的视角,讨论如何通过遵循一系列原则,使用动态装饰器来扩展组件功能并解决横切关注点。
什么是动态装饰器
动态装饰器,在文章 动态装饰器模式 中首次提出,是从装饰器模式演变而来的。它解决了与装饰器模式相同的问题:动态地为现有对象添加附加功能。然而,动态装饰器采取了一种完全不同的方法来解决问题。它不创建装饰类,而是使用 .NET 远程处理技术来拦截方法调用,并在目标方法执行之前或之后应用附加功能。动态装饰器的一个优点是它不需要设计/维护装饰类。而且它易于使用,可以应用于任何组件的任何对象。
在文章 使用动态装饰器为对象添加方面 中,描述了动态装饰器的各种特性,并讨论了在运行时动态地为对象添加方面的不同场景。使用动态装饰器,您可以通过为每个方面提供一个方法来为应用程序添加方面。然后,现有对象(无论是相同类型还是不同类型)都可以通过这些方面在运行时进行装饰。
有关详细信息,请参阅文章 动态装饰器模式 和 使用动态装饰器为对象添加方面。我强烈建议您在继续阅读之前先阅读这两篇文章。
使用动态装饰器进行应用程序开发
在应用程序开发中,您会使用各种组件来构建您的应用程序。有些组件是您自己设计的。有些来自框架。有些来自第三方库。无论组件来自何处,您都会面临两个常见问题:扩展组件以获得附加功能,以及解决应用程序的系统级横切关注点。
动态装饰器自然适合解决这些问题。正如您在上面的文章中所见,使用动态装饰器非常容易地扩展组件功能或为对象添加方面。在本文中,我将从应用程序开发的视角,讨论如何系统地使用动态装饰器来扩展组件并解决横切关注点。正确使用它,您将拥有更简单、更易于维护的应用程序。
扩展组件功能
谈到扩展组件功能,您可能会首先想到编写一个派生自该组件的新类,然后在应用程序中使用新类的对象。虽然这在扩展组件功能方面总是有效的,但它通常会产生其他问题,例如类爆炸。您最终可能会有很多类需要维护。或者,如果您拥有组件的源代码,您可以直接修改组件代码以添加新功能。这些方法不太理想。它们违反了单一职责原则(一个类应该只有一个改变的理由)和开闭原则(软件实体应该是对扩展开放的,但对修改关闭的)。违反这些原则可能会在大型软件系统中造成巨大的维护工作。
通常,您是否需要组件的附加功能,直到您在应用程序中使用该组件的对象时才能明确,即,当您尝试调用该对象的方法时。例如,假设一个员工存储库组件的 GetAll
方法返回一个 Employee
对象列表。根据应用程序的情况,您可能需要按姓氏、生日或其他标准对列表进行排序,也可能不需要。
使用上述方法,如果组件不支持您的排序要求,您将编写一个派生自该组件的新类来添加排序功能,或者如果您拥有组件的源代码,则修改组件代码来添加排序功能。然后,您创建并使用新类或修改后的组件的对象。之后,如果您需要按不同的标准对列表进行排序,或者您需要为组件的另一个方法添加一些附加功能,您将需要修改您创建的类,创建另一个类,或者再次修改组件代码。最终,您将拥有大量类或开放的组件。
扩展组件功能的另一种方法是使用装饰器模式。装饰器模式的好处在于它可以在运行时根据特定情况为特定对象扩展功能。然而,它仍然需要设计装饰类,并遭受与上述方法相同的类爆炸和维护问题。
最后但同样重要的是,您可以使用动态装饰器来扩展组件的功能。正如您可能通过阅读 动态装饰器模式 文章已经知道的那样,为组件对象添加附加功能就像编写一个方法一样简单。以刚才的例子为例。如果您需要按姓氏对员工存储库组件的对象列表进行排序,您可以编写一个排序方法并调用动态装饰器。现在,该对象具有排序功能。
一个直接的好处是,无需为扩展组件而创建类或修改组件代码。它也有利于组件开发过程中的组件设计。您以通用方式设计您的组件以满足业务需求。当组件在应用程序中使用时,如果需要附加功能,则使用动态装饰器在运行时扩展组件的对象。这样,只有实例获得了附加功能,独立于该组件的其他实例。最终结果是您拥有一个封闭的组件和更精简的应用程序。
解决横切关注点
横切关注点是程序中依赖或必须影响系统其他许多部分的部分。这些关注点通常无法在设计和实现中从系统的其余部分干净地分解出来,并可能导致代码分散(代码重复)、纠缠(大量依赖)或两者兼有。
面向方面编程 (AOP) 旨在将横切关注点封装到方面中。然而,AOP 在面向组件的开发中的应用仍然非常有限。我认为主要原因是大多数 AOP 工具试图在组件设计时解决方面问题。由于您无法预测组件用户使用组件的每一种场景,因此很难决定一个组件是否需要方面。以 Employee
组件为例。如果一个应用程序只有几个 Employee
实例,为该组件添加日志记录方面可能是可以的。但如果一个应用程序有数百万个 Employee
实例,那么为该组件添加日志记录方面可能不是个好主意。最糟糕的是,如果一个应用程序开始时只有几个员工,您决定为 Employee
添加日志记录方面。然后它发展到数十万名员工。现在,您的应用程序忙于编写员工日志。这就是为什么会把自己坑了。
横切关注点是动态变化的。通常,在组件设计时没有足够的信息来解决它们。它们应该在组件对象在应用程序中使用时在对象级别上解决。当您在应用程序中使用组件的对象时,您确切地知道您希望该对象具有或不具有哪些方面。例如,当员工访问某些敏感数据(例如,其他员工的工资)时,您可能希望记录该员工。当您生成公司所有员工的报告时,您可能不希望记录所有员工。不幸的是,当今大多数 AOP 工具不支持对象级别的方面,并且没有添加所需方面的灵活性。
正如您可能通过阅读文章 使用动态装饰器为对象添加方面 已经知道的那样,当组件对象在应用程序中使用时,动态装饰器可以用来在对象级别添加方面。您可以按原样使用对象。或者,您可以使用动态装饰器为其添加方面。以刚才的例子为例,如果您希望在员工访问敏感数据时记录该员工,您可以通过传递具有日志记录方面的对象来调用动态装饰器。现在,该对象具有日志记录功能。如果您不希望在生成公司所有员工的报告时记录员工,只需按原样使用它们。
在对象使用时为其添加方面对组件开发具有重要意义。首先,组件与横切关注点完全分离,并且应该只为满足业务需求而进行通用设计。这样,您就可以获得更通用、更稳定的组件。其次,方面仅在需要时才添加到组件的对象,而独立于应用程序中该组件的其他实例。这使得应用程序更精简、更高效。
使用动态装饰器进行 AOP 的另一个优点是,一个方面可以被不同的对象共享,这些对象可以是同一组件类型,也可以是不同组件类型。如果设计得当,您的应用程序可以只有几个方面(日志记录、身份验证、授权、仪表化等),它们可以应用于各种组件类型的对象(Employee
、Department
、Transaction
等)。并且这些对象可以位于应用程序的不同层(数据层、服务层、业务层、UI 层等)。您也可以将方面放在自己的模块中。这样,不同的应用程序可以共享相同的方面。最终,这些方面可以应用于不同组件类型的对象,跨应用程序层,或跨应用程序。
应用程序开发指南
总的来说,应用程序开发涉及遵循定义的流程和事件处理模型来构建和使用各种组件的对象。根据您的应用程序类型(WinForms、ASP.NET 表单、ASP.NET MVC、Silverlight 等),有不同的方法来定义流程和处理事件。无论您的应用程序类型如何,您都会面临一些共同的任务,例如设计组件、扩展组件以及为您的应用程序解决横切关注点。以下指南提供了处理应用程序开发中这些常见任务的原则
- 以通用方式设计组件以满足业务需求
- 将方面设计为各自模块中的全局方法
- 按需为对象添加方面
- 按需扩展对象
正如您在接下来的讨论和示例中看到的那样,动态装饰器的使用使得遵循这些原则变得容易。
本文不讨论如何设计组件。然而,它强调组件应该以通用方式设计以满足业务需求。目标是使组件更通用、更稳定。以员工存储库组件的 GetAll
方法为例。您是否需要为该组件提供五种不同的排序方式?如果客户以后需要另一种排序方式怎么办?组件的所有对象是否真的需要这五种不同的排序方式?如果您试图将所有这些功能都包含在组件中,您最终将得到一个庞大且开放的组件。所以,最好推迟到组件在应用程序中使用时再决定是否实现这些具体功能。
使用动态装饰器,方面是一个方法(命名或匿名)。由于一个方面可能被不同的对象使用,将其放入自己的模块或模块中作为独立的全局方法是有意义的。这样,您可以设计您组织范围内的方面,并且这些方面可以被应用程序内或跨应用程序的同一组件类型或不同组件类型的对象使用。
使用动态装饰器,向对象添加方面在语法上与扩展对象没有区别。您只需通过传递一个方法来调用对象的动态装饰器。然而,由于一个方面很可能被多个对象共享,因此最好将其设为一个全局方法。另一方面,在扩展对象时,将匿名方法传递给动态装饰器更方便,因为它通常是一次性的。
一个例子是帮助您理解上述讨论的最佳方式。因此,在接下来的几节中,我将展示一个遵循上述指南开发的完整应用程序。
一个示例
在接下来的几节中,将以 HRForms 这个小型应用程序为例,遵循应用程序开发指南进行开发。该应用程序基于部门选择显示员工,并实现为一个 WinForms 应用程序。
Components
假设有两个组件,Employee
和 Department
。对于 Employee
,有一个相应的 RepositoryEmployee
组件来包含 Employee
对象的集合。对于 Department
,有一个相应的 RepositoryDepartment
组件来包含 Department
对象的集合。这些组件的代码在此处列出
public interface IEmployee
{
System.Int32? EmployeeID { get; set; }
System.String FirstName { get; set; }
System.String LastName { get; set; }
System.DateTime DateOfBirth { get; set; }
System.Int32? DepartmentID { get; set; }
System.String FullName();
System.Single Salary();
}
public class Employee : IEmployee
{
#region Properties
public System.Int32? EmployeeID { get; set; }
public System.String FirstName { get; set; }
public System.String LastName { get; set; }
public System.DateTime DateOfBirth { get; set; }
public System.Int32? DepartmentID { get; set; }
#endregion
public Employee(
System.Int32? employeeid
, System.String firstname
, System.String lastname
, System.DateTime bDay
, System.Int32? departmentID
)
{
this.EmployeeID = employeeid;
this.FirstName = firstname;
this.LastName = lastname;
this.DateOfBirth = bDay;
this.DepartmentID = departmentID;
}
public Employee() { }
public System.String FullName()
{
System.String s = FirstName + " " + LastName;
return s;
}
public System.Single Salary()
{
System.Single i = 10000.12f;
return i;
}
}
public interface IDepartment
{
System.Int32? DepartmentID { get; set; }
System.String Name { get; set; }
}
public class Department : IDepartment
{
#region Properties
public System.Int32? DepartmentID { get; set; }
public System.String Name { get; set; }
#endregion
public Department(
System.Int32? departmentid
, System.String name
)
{
this.DepartmentID = departmentid;
this.Name = name;
}
public Department() { }
}
public interface IRepository<T>
{
List<T> RepList { get; set; }
void GetAll();
}
public class RepositoryEmployee : IRepository<IEmployee>
{
private List<IEmployee> myList = null;
public List<IEmployee> RepList
{
get { return myList; }
set { myList = value; }
}
public RepositoryEmployee()
{
}
public void GetAll()
{
myList = new List<IEmployee> {
new Employee(1, "John", "Smith", new DateTime(1990, 4, 1), 1),
new Employee(2, "Gustavo", "Achong", new DateTime(1980, 8, 1), 1),
new Employee(3, "Maxwell", "Becker", new DateTime(1966, 12, 24), 2),
new Employee(4, "Catherine", "Johnston", new DateTime(1977, 4, 12), 2),
new Employee(5, "Payton", "Castellucio", new DateTime(1959, 4, 21), 3),
new Employee(6, "Pamela", "Lee", new DateTime(1978, 9, 16), 4) };
}
}
public class RepositoryDepartment : IRepository<IDepartment>
{
private List<IDepartment> myList = null;
public List<IDepartment> RepList
{
get { return myList; }
set { myList = value; }
}
public RepositoryDepartment()
{
}
public void GetAll()
{
myList = new List<IDepartment> {
new Department(1, "Engineering"),
new Department(2, "Sales"),
new Department(3, "Marketing"),
new Department(4, "Executive") };
}
}
在此应用程序中,员工和部门的数据被硬编码在两个列表中以简化我们的讨论。在实际应用程序中,这些数据通常存储在关系数据库中。然后,您需要创建一个数据层来检索它们并将它们放入列表中。
还值得注意的是,员工列表是按插入顺序填充的,没有任何排序。目前很难预测需要为该组件支持什么样的排序。在应用程序中,组件的一个对象可能需要按姓氏排序。另一个可能需要按生日排序。第三个可能根本不需要排序。所以,最好推迟到组件在应用程序中使用时再实现排序功能。
HRForms
HRForms 是一个 WinForms 应用程序,它使用上述组件来根据部门选择显示员工。由于这是一个 WinForms 应用程序,它遵循 WinForms 的工作流程和事件模型。代码在此处列出
public partial class DepEmpForm : Form
{
private IRepository<IEmployee> rpEmployee = null;
private IRepository<IDepartment> rpDepartment = null;
private static int iStaticDep = 0;
private bool bLoaded = false;
public DepEmpForm()
{
InitializeComponent();
rpEmployee = new RepositoryEmployee();
rpDepartment = new RepositoryDepartment();
}
private void DepEmpForm_Load(object sender, EventArgs e)
{
bLoaded = false;
try
{
rpDepartment.GetAll();
comboBox1.DataSource = rpDepartment.RepList;
comboBox1.ValueMember = "DepartmentID";
comboBox1.DisplayMember = "Name";
comboBox1.SelectedIndex = -1;
rpEmployee.GetAll();
dataGridView1.DataSource = rpEmployee.RepList;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
bLoaded = true;
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox1.SelectedValue != null)
{
IDepartment dpSel = (IDepartment)comboBox1.SelectedItem;
iStaticDep = dpSel.DepartmentID.Value;
List<IEmployee> empSel = null;
if (rpEmployee.RepList != null)
{
empSel = rpEmployee.RepList.FindAll(
(IEmployee emp) => { return emp.DepartmentID.Value == iStaticDep; });
}
dataGridView1.DataSource = empSel;
}
}
}
运行时,所有员工显示如下
选择部门后,将显示该部门的员工。
扩展员工列表以进行排序
现在,假设您想按姓氏对员工进行排序。由于 RepositoryEmployee
有一个无序的员工列表,您需要扩展该组件以对其列表进行按姓氏排序。您需要做以下事情。
首先,您创建一个用于排序的比较器类,如下所示
internal class EmployeeLastnameComparer : IComparer<IEmployee>
{
public int Compare(IEmployee e1, IEmployee e2)
{
return String.Compare(e1.LastName, e2.LastName);
}
}
然后,在事件处理程序 DepEmpForm_Load
中,在 rpEmployee.GetAll()
之前调用动态装饰器,如下所示
rpEmployee = (IRepository<IEmployee>)ObjectProxyFactory.CreateProxy(
rpEmployee,
new String[] { "GetAll" },
null,
new Decoration((x, y) =>
{
object target = x.Target;
if (target.GetType().ToString() == "ThirdPartyHR.RepositoryEmployee")
{
List<IEmployee> emps = ((IRepository<IEmployee>)target).RepList;
IEnumerable<IEmployee> query = emps.OrderByDescending(emp => emp,
new EmployeeLastnameComparer()).ToList<IEmployee>();
((IRepository<IEmployee>)target).RepList = (List<IEmployee>)query;
}
}, null));
就这样。现在,HRForms 按姓氏对员工进行排序显示。构建并运行它。您将看到员工按姓氏排序显示如下
当您选择一个部门时,将显示与该部门关联的员工,并按姓氏排序。
请注意,使用了 Lambda 表达式为该员工存储库对象提供了一个匿名方法来添加排序功能。当然,您也可以使用常规方法来实现排序逻辑。但是,由于此排序逻辑特别针对员工存储库对象 rpEmployee
并且不被其他对象共享,因此将其保留在匿名方法中更简洁。
设计方面
假设您想为您的 HRForms 应用程序处理进入/退出日志记录和安全检查的横切关注点。这些方面被放在一个名为 SysConcerns
的类中,作为独立的公共方法,并打包在自己的模块中。以下是这些关注点的代码
public class SysConcerns
{
public static void EnterLog(AspectContext ctx, object[] parameters)
{
IMethodCallMessage method = ctx.CallCtx;
string str = "Entering " + ctx.Target.GetType().ToString() +
"." + method.MethodName +
"(";
int i = 0;
foreach (object o in method.Args)
{
if (i > 0)
str = str + ", ";
str = str + o.ToString();
}
str = str + ")";
Console.WriteLine(str);
}
public static void ExitLog(AspectContext ctx, object[] parameters)
{
IMethodCallMessage method = ctx.CallCtx;
string str = "Exiting " + ctx.Target.GetType().ToString() +
"." + method.MethodName +
"(";
int i = 0;
foreach (object o in method.Args)
{
if (i > 0)
str = str + ", ";
str = str + o.ToString();
}
str = str + ")";
Console.WriteLine(str);
}
public static void AdminCheck(AspectContext ctx, object[] parameters)
{
if (parameters != null &&
((WindowsPrincipal)parameters[0]).IsInRole(
"BUILTIN\\" + "Administrators"))
{
Console.WriteLine("Has right to call");
return;
}
throw new Exception("No right to call!");
}
}
EnterLog
写入进入日志,而 ExitLog
写入退出日志。AdminCheck
验证应用程序的用户是否为管理员。
当然,您可以根据您的系统要求修改这些方法以执行任何您想要的操作。它们可以通过访问上下文、目标和输入参数中的各种信息来增强。
使用方面
一旦定义了方面,您就可以根据需要将其添加到对象中。
假设您想在调用 RepositoryDepartment
组件的存储库对象 rpDepartment
的 GetAll
方法之前添加安全检查方面。您还想向同一个对象添加进入日志和退出日志。您将以下代码添加到窗体加载事件处理程序 DepEmpForm_Load
中 rpDepartment.GetAll()
之前。
rpDepartment = (IRepository<IDepartment>)ObjectProxyFactory.CreateProxy(
rpDepartment,
new String[] { "GetAll" },
new Decoration(new DecorationDelegate(SysConcerns.AdminCheck),
new object[] { Thread.CurrentPrincipal }),
null);
rpDepartment = (IRepository<IDepartment>)ObjectProxyFactory.CreateProxy(
rpDepartment,
new String[] { "GetAll" },
new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
new Decoration(new DecorationDelegate(SysConcerns.ExitLog), null));
然后,假设您想向 RepositoryEmployee
组件的对象 rpEmployee
的 GetAll
方法添加进入日志和退出日志;只需在窗体加载事件处理程序 DepEmpForm_Load
中 rpEmployee.GetAll()
之前插入以下代码。
rpEmployee = (IRepository<IEmployee>)ObjectProxyFactory.CreateProxy(
rpEmployee,
new String[] { "GetAll" },
new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
new Decoration(new DecorationDelegate(SysConcerns.ExitLog), null));
最后,假设您想跟踪访问了哪个部门;您可以在部门选择事件处理程序 comboBox1_SelectedIndexChanged
中,在使用选定对象 dpSel
的 Department
组件的 iStaticDep = dpSel.DepartmentID.Value
属性之前添加以下代码。
if (bLoaded == true)
{
dpSel = (IDepartment)ObjectProxyFactory.CreateProxy(
dpSel,
new String[] { "get_DepartmentID" },
new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
null);
}
这样就完成了 HRForms 应用程序的组件扩展和横切关注点的处理。
为了方便您,扩展组件并添加方面后的 HRForms 代码在此处列出
public partial class DepEmpForm : Form
{
internal class EmployeeLastnameComparer : IComparer<IEmployee>
{
public int Compare(IEmployee e1, IEmployee e2)
{
return String.Compare(e1.LastName, e2.LastName);
}
}
private IRepository<IEmployee> rpEmployee = null;
private IRepository<IDepartment> rpDepartment = null;
private static int iStaticDep = 0;
private bool bLoaded = false;
public DepEmpForm()
{
InitializeComponent();
rpEmployee = new RepositoryEmployee();
rpDepartment = new RepositoryDepartment();
}
private void DepEmpForm_Load(object sender, EventArgs e)
{
bLoaded = false;
try
{
rpDepartment = (IRepository<IDepartment>)ObjectProxyFactory.CreateProxy(
rpDepartment,
new String[] { "GetAll" },
new Decoration(new DecorationDelegate(SysConcerns.AdminCheck),
new object[] { Thread.CurrentPrincipal }),
null);
rpDepartment = (IRepository<IDepartment>)ObjectProxyFactory.CreateProxy(
rpDepartment,
new String[] { "GetAll" },
new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
new Decoration(new DecorationDelegate(SysConcerns.ExitLog), null));
rpDepartment.GetAll();
comboBox1.DataSource = rpDepartment.RepList;
comboBox1.ValueMember = "DepartmentID";
comboBox1.DisplayMember = "Name";
comboBox1.SelectedIndex = -1;
//Add sorting logic to employee list
rpEmployee = (IRepository<IEmployee>)ObjectProxyFactory.CreateProxy(
rpEmployee,
new String[] { "GetAll" },
null,
new Decoration((x, y) =>
{
object target = x.Target;
if (target.GetType().ToString() == "ThirdPartyHR.RepositoryEmployee")
{
List<IEmployee> emps = ((IRepository<IEmployee>)target).RepList;
IEnumerable<IEmployee> query = emps.OrderByDescending(emp => emp,
new EmployeeLastnameComparer()).ToList<IEmployee>();
((IRepository<IEmployee>)target).RepList = (List<IEmployee>)query;
}
}, null));
//Add entering log to employee list
rpEmployee = (IRepository<IEmployee>)ObjectProxyFactory.CreateProxy(
rpEmployee,
new String[] { "GetAll" },
new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
new Decoration(new DecorationDelegate(SysConcerns.ExitLog), null));
rpEmployee.GetAll();
dataGridView1.DataSource = rpEmployee.RepList;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
bLoaded = true;
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox1.SelectedValue != null)
{
IDepartment dpSel = (IDepartment)comboBox1.SelectedItem;
if (bLoaded == true)
{
dpSel = (IDepartment)ObjectProxyFactory.CreateProxy(
dpSel,
new String[] { "get_DepartmentID" },
new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
null);
}
iStaticDep = dpSel.DepartmentID.Value;
List<IEmployee> empSel = null;
if (rpEmployee.RepList != null)
{
empSel = rpEmployee.RepList.FindAll(
(IEmployee emp) => { return emp.DepartmentID.Value == iStaticDep; });
}
dataGridView1.DataSource = empSel;
}
}
}
需要注意的是,由 ObjectProxyFactory.CreateProxy
返回的对象被重新赋值给最初指向目标变量。例如,rpEmployee
最初被赋值为 RepositoryEmployee
的对象——目标。调用 ObjectProxyFactory.CreateProxy
后,它被赋值为返回的对象,即目标的代理。这一点很微妙但很重要。ObjectProxyFactory.CreateProxy
返回的对象是目标的代理。通过使用相同的变量来表示目标及其代理,原始代码保持不变。这意味着目标及其代理是可互换的。如果变量指向目标,则按原样使用目标。如果变量指向目标的代理,则在目标使用之前或之后会执行附加功能。实际上,如果您删除所有调用 ObjectProxyFactory.CreateProxy
的代码,您将得到在扩展组件和添加方面之前的原始代码。
最后,在运行应用程序之前,您需要修改 Main
方法,将控制台输出重定向到文件 hrlog.txt 并设置安全策略。这些修改仅适用于此应用程序,因为进入/退出日志记录方面使用控制台,而安全检查方面使用 Window 安全策略。您的应用程序可能使用不同的日志记录机制和安全策略。在这种情况下,您需要进行相应的更改。
static void Main()
{
string path = Path.GetDirectoryName(Application.ExecutablePath);
FileStream fileStream = null;
if (!File.Exists(path + "\\hrlog.txt"))
{
fileStream = new FileStream(path + "\\hrlog.txt", FileMode.Create);
}
else
fileStream = new FileStream(path + "\\hrlog.txt", FileMode.Truncate);
TextWriter tmp = Console.Out;
StreamWriter sw1 = new StreamWriter(fileStream);
Console.SetOut(sw1);
Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new DepEmpForm());
Console.SetOut(tmp);
sw1.Close();
fileStream.Close();
}
应用程序运行时,您将在文件 hrlog.txt 中看到以下输出。
Entering ThirdPartyHR.RepositoryDepartment.GetAll()
Has right to call
Exiting ThirdPartyHR.RepositoryDepartment.GetAll()
Entering ThirdPartyHR.RepositoryEmployee.GetAll()
Exiting ThirdPartyHR.RepositoryEmployee.GetAll()
Entering ThirdPartyHR.Department.get_DepartmentID()
注意:在源代码中,HRForms 项目包含扩展组件和添加方面之前的初始代码。HRFormsExtended 项目包含扩展组件和添加方面之后的代码。
关注点
应用程序开发涉及一些共同的任务,例如设计组件、扩展组件、设计方面和使用方面。动态装饰器有助于系统地解决这些问题。特别是,动态装饰器使得能够轻松地
- 以通用方式设计组件以满足业务需求
- 将方面设计为各自模块中的全局方法
- 按需为对象添加方面
- 按需扩展对象