返回首页


{S0}背景
你有没有需要处理关闭一个肮脏的形式,已保存的数据,如客户信息?你有没有像这样结束吗?

1   public partial class CustomerEditForm : Form

2   {

3       ...        

4       private void View_FormClosing(object sender, FormClosingEventArgs e)

5       {

6          if (bDirty)

7           {

8               DialogResult result = MessageBox.Show("Save the changes?", 

                            "Save the changes?", MessageBoxButtons.YesNo);

9               if (result == DialogResult.Yes)

10              {

11                  ... // Save the changes.

12              }

13              else

14              {

15                  ... // Discard the changes.

16              }

17          }

18          ...

19      }

20      private void textBoxLastName_TextChanged(object sender, EventArgs e)

21      {

22          bDirty = true;

23          ...

24      }

25      ... // Set the flag in the rest of the Control_StuffChanged() methods.

26  }

问题
我所看到的或在工作中这几次,为我校项目等,最近,我没有再一点点有趣的项目,得到它咬伤。首先,我有时忘了设置标志的地方。其次,quot; dirtyquot;代码是如此分散了,标志设置/重置意外YesNo对话框弹出不足为奇次。第三,脏标志不能反映脏数据的定义。如果用户撤销的变化,这些数据是不脏,但应用程序仍然弹出YesNo对话框。最后,和最差的,如果我需要在另一种形式相同的能力,我不得不再次编写相同的代码,造成重复代码。此外,分别采用了不同的问题YesNo消息框由于重复的代码,使用户不得不仔细阅读的问题不是选择了错误的选择。
,那岂不是很好的巩固脏的处理代码中的一个组成部分和使用任何形式的?与OOP,它是可能的,但不容易。该计划
该计划是使用面向方面编程(AOP)。本文使用Spring.Net AOP模块。我将假设你有基本的了解和Spring.Net在Spring.Net AOP模块。 9日和17日在Spring.Net 1.0.2手册章节做一个出色的工作引进AOP。
的基本思想是抽象的和移动的肮脏的处理代码(6日至17日和22行等)一个肮脏的顾问,让Form类将脏的处理代码。我们拦截窗体关闭事件和肮脏的处理代码在运行时执行。然后,在肮脏的处理代码,其余表格闭幕代码恢复。
我们也取代bDirty标志备忘录模式来解决这个问题,当标志没有真正捕获的quot定义; dirtyquot。留念的对象存储的应用程序的数据,如状态Customer对象。我们将测试两个纪念品,以确定数据的肮脏的平等。同样,我们拦截窗体加载事件,捕捉到后来比较应用程序的数据。类DirtyHandlingAspect
我们将所有DirtyHandlingAspect脏的处理代码。在这方面,两方面的工作需要做的工作:事件后建立的基线数据,例如:表格是可见的。肮脏的处理代码之前运行的事件,例如表格关闭。
在每个任务中,我们需要确定的事件或方法调用拦截和周围的事件执行的行为。在Spring.Net AOP,每个任务是仿照作为顾问。下图显示了我们DirtyHandlingAspect的模型。
IOriginator
之前我们谈论DirtyHandlingAspect的内容,一个重要的接口,IOriginator的,需要被提及。对于DirtyHandlingAspect工作,它需要截取的对象的内部状态,并保存数据的方法。形式调用CreateMemento IOriginator截获的对象的状态,同时保存被称为保存数据。 DirtyHandlingAspect截获的任何对象必须实现这个接口。换句话说,只要一个对象实现IOriginator,DirtyHandlingAspect可以添加脏处理能力的对象。背后IOriginator隐藏的截获对象的implementnation细节。
的名称IOriginator是借用的纪念品的设计模式,其中的一个对象的内部状态是所谓的纪念品和对象是所谓的鼻祖。
备忘录对象是对象类型。对于DirtyHandlingAspect,备忘录对象的唯一的要求是对象标识方法,如GetHashCode和"等于",实施适当的。Spring.Net顾问
DirtyHandlingAspect有两个Spring.Net AOP的顾问。一个Spring.Net AOP顾问有两个组成部分。切入点标识的连接点,例如:被拦截的对象的方法,进行拦截,而拦截的对象称为目标。建议定义的连接点插入的代码。这两个顾问继承DefaultPointcutAdvisor在类图中所示。BaselineAdvisor
BaselineAdvisor实现DirtyHandlingAspect第一任务。 BaselineAdvisor代码很简单:{C}
构造函数是这个类有一个正则表达式作为其参数。正则表达式用于创建类型SdkRegularExpressionMethodPointcut切入点。顾名思义,SdkRegularExpressionMethodPointcut识别方法拦截使用正则表达式。 DirtyHandlingAspect第一任务需要BaselineAdvisor拦截状的形式加载的事件。 methodNameRE可以像的OnLoad的东西。它是谁使用适当的正则表达式DirtyHandlingAspect通过方法名。
捕获应用程序的数据纪念品的代码是定义在BaselineAdvice。被调用时,BaselineAdvice调用目标的CreateMemento。实施IMethodInterceptor,BaselineAdvice可以把方法调用之前和之后的,它的代码,你可以看到下面的代码片段。请注意,IMet​​hodInvocation.This点目标,IOriginator类型的对象。
1   sealed class BaselineAdvice : IMethodInterceptor

2   {

3       private object _baselineMemento;

4       public object BaselineMemento

5       {

6           get

7           {

8               return _baselineMemento;

9           }

10      }

11      public object Invoke(IMethodInvocation method)

12      {

13          IOriginator target = (IOriginator)method.This;  // target is the Presenter

14          _baselineMemento = target.CreateMemento();      // get and store the state of the Model

15          return method.Proceed();                        // continue the program execution.

16      }

17  }


BaselineAdvisor应适用于表格OnLoad事件,所以可以采取尽快的形式显示应用程序的数据纪念品。HandleDirtyAdvisor
HandleDirtyAdvisor实现的DirtyHandlingAspect第二任务。 BaselineAdvisor类似,它使用SdkRegularExpressionMethodPointcut。搬到这里,是在旧CustomerEditForm的脏的处理代码:
1   sealed class HandleDirtyAdvice : IMethodInterceptor                                                                   

2  {

3       private BaselineAdvice _baselineAdvice;

4       public HandleDirtyAdvice(BaselineAdvice baselineAdvice)

5       {

6           _baselineAdvice = baselineAdvice;

7       }

8   

9       public object Invoke(IMethodInvocation method)

10{

11          IOriginator target = (IOriginator)method.This;  // target is the presenter now.

12          object currentMemento = target.CreateMemento(); // Get the current state of the Model.

13          if (!currentMemento.Equals(_baselineAdvice.BaselineMemento))                       

14          {   // Show the YesNo dialog if the Memento's are not equal(dirty).

15              DialogResult result = MessageBox.Show("Save the changes?", this.GetType().Name,

                    MessageBoxButtons.YesNo); 

16              if (result == DialogResult.Yes)

17              {

18                  target.Save(); 

19              }

20          } 

21          return method.Proceed();  // continue with the program execution with the intercepted method.

22      }

23  }   


行13-20非常相似,在CustomerEditForm老实施。被调用时,它得到的数据纪念品和比较基线在使用equals BaselineAdvice纪念品。如果两个纪念品是不相等的(脏)的,YesNo对话框弹出。如果用户选择"是",建议在目标上调用保存到保存数据。第21行恢复执行的方法/事件。
HandleDirtyAdvisor应该被称为窗体关闭事件或类似。CustomerEditView和CustomerEditPresenter
Spring.Net AOP只拦截方法,接口中定义的一个对象。一个老CustomerEditForm的小的重构是必要的,我们将MVP模式重构它。稍后您将看到,通过移动MVP的设计允许Spring.Net AOP自动拦截利益的方法。
总是一个很好的做法,在UI设计中使用MVP模式,但我不会详述了MVP模式。你可以找到MVP文章在"参考"部分的链接。下面的类图显示了重构设计。
的注意,ICustomerEditPresenter有两个有趣的方法,即onLoad和OnClosing。 CustomerEditView代表的形式加载和关闭通过这两种方法和DirtyHandlingAspect的事件,以CustomerEditPresenter将拦截他们。由于CustomerEditPresenter实现ICustomerEditPresenter,CustomerEditPresenter是准备被截获。IOriginator
在上一节所讨论的,被拦截的对象,CustomerEditPresenter需要实现IOriginator。以下是两种方法在CustomerEditPresenter的代码。
    public class CustomerEditPresenter: ICustomerEditPresenter, IOriginator

    {

        ....

        public void Save()

        {

            MessageBox.Show("Save() is called. Save the changes.");

        }

        public object CreateMemento()

        {

            Memento customer = new Memento();  // 

            customer.FirstName = _Customer.FirstName;

            customer.LastName = _Customer.LastName;

            customer.PhoneNumber = _Customer.PhoneNumber;

            return customer;

        }

        ....

    }


演示目的,保存只显示一个MessageBox CreateMemento副本和客户对象的数据,以作纪念,这等于适当的执行。创建代理
所需的最后一件事是拦截DirtyHandlingAspect CustomerEditPresenter。 Spring.Net AOP的说法,我们需要一个CustomerEditPresenter代理。自创建一个代理对象是复杂的,我们创建了一个工厂类,DirtyHandlingAspectProxyFactory封装的过程。以下是从DirtyHandlingAspectProxyFactory类的代码。
1   public sealed class DirtyHandlingProxyFactory

2   {

3       private string _baselineMethodRE;

4       private string _closingMethodsRE;

5       public DirtyHandlingProxyFactory(string baselineMethodsRE, string closingMethodsRE)

6       {

7           _baselineMethodRE = baselineMethodsRE;

8           _closingMethodsRE = closingMethodsRE;

9       }

10      public object GetProxy(object origin)

11      {

12          if (!(origin is IOriginator))

13              throw new Exception("origin must be of type IOriginator.");

14          ProxyFactory proxyFactory = new ProxyFactory(origin);

15          DirtyHandlingAspect dirtyHandlingAspect = new DirtyHandlingAspect(_baselineMethodRE,

                _closingMethodsRE);

16          foreach (DefaultPointcutAdvisor advisor in dirtyHandlingAspect.Advisors)

17          {

18              proxyFactory.AddAdvisor(advisor);

19          }

20          return proxyFactory.GetProxy();

21      }

22  }


的构造函数有两个正则表达式字符串。首先确定留念后,创建和存储作为基准和传递的BaselineAdvisor构造方法调用。第二个正则表达式的选择执行后,脏处理代码​​,用于创建一个HandleDirtyAdvice对象的方法调用。 GetProxy(对象)DirtyHandlingProxyFactory的方法创建应用DirtyHandlingAspect顾问代理的由来。线12-13检查,如果原产地类型IOriginator是因为我们的意见仅IOriginator类型的对象。第14行创建的Spring.Aop.Framework.ProxyFactory。 15-19行创建和添加DirtyHandlingAspect顾问ProxyFactory里对象。在第21行,ProxyFactory.GetProxy()创建代理,使用在第18行添加顾问。代理,然后返回。使用的DirtyHandlingAspect
现在,我们需要做的是创建使用DirtyHandlingProxyFactory CustomerEditPresenter代理,然后使用代理。这是很容易做到:
1   DirtyHandlingProxyFactory proxyFactory = 

         new DirtyHandlingProxyFactory("OnLoad", "OnClosing");

2   ICustomerEditPresenter customerEditPresenter = 

         (ICustomerEditPresenter)proxyFactory.GetProxy(new CustomerEditPresenter());


1号线告诉工厂绑定BaselineAdvice目标和HandleDirtyAdvice OnClosing方法OnLoad方法。 2号线将由两个顾问截获创建一个CustomerEditPresenter代理。HandleDirtyAdvice在行动
让我们来看看如何一起。下面的序列图描述时会发生什么的CustomerEditPresenter OnClosing调用。
{S3}
注意的刻板印象顾问。顾问接受所有来电和重定向请求,根据切入点规范。在这种情况下,因为方法名称OnClosing(步骤1)与正则表达式在SdkRegularExpressionMethodPointcut匹配,调用Invoke的HandleDirtyAdvice方法(步骤1.1)。 HandleDirtyAdvice流的其余部分很简单。重用的DirtyHandlingAspect
如果DirtyHandlingAspect CustomerEditPresenter有用的AOP不会增加任何价值在这里。事实上,精心设计隔离脏处理代码​​在DirtyHandlingAspect,所以我们可以重用DirtyHandlingAspect。示例项目StateChooser说明了这一点。
脏处理功能添加到您的项目中,重构实施IOriginator将被拦截的类。被截获的方法应该是定义一个接口。记住要检查,如果纪念品类实现Equals和GetHashCode的适当。使用DirtyHandlingProxyFactory创建代理,然后使用代理的应用程序的其余部分。改进的DirtyHandlingAspect
示例代码是绝不优化或bugless简化教学的原因。有改进的余地。例如:使用更好的RegularExpressionMethodPointcutAdvisor如顾问,而不是DefaultPointcutAdvisor支持YesNoCancel对话框。结论
我们已经表明,一个小的重构,脏的处理代码,一度分散在各类可以提取使用AOP的一个方面。试想一下,如何清洁您的代码库可以没有重复的,各种形式类的脏的处理代码。方面可以用来添加其他组件相同的脏处理功能,并不仅限于UI组件。为了重复使用DirtyHandlingAspect,只需执行IOriginator延长,为作纪念,等于如果需要的组件。使用的DirtyHandlingAspect GetProxy方法添加一个错误处理方面的组件。
本文更广泛的消息表明,AOP可以用来实现横切功能要求。 AOP已经持续一段时间,并能够解决横切关注点的吹捧。我们使用方面,以解决非功能性需求,这通常是横切关注点,但我们仍然采取面向对象的解决横切功能要求时。事实上,一流的公民,他们的同胞的类一样,应被视为方面。方面应该拿出自然在我们日常的软件设计,主机横切逻辑。
这篇文章背后的想法是启发书quot;面向方面的软件开发与使用Casesquot;伊瓦雅各布森和泛伟伍。书中还介绍了一种系统化的方法来识别横切用例。令人吃惊的是,有许多领域,AOP可以创建简单的软件有很大的帮助。
享受!参考文献伊瓦雅各布森和泛伟伍用例的面向方面软件开发(ISBN:0321268881){A}历史2006-09-18:新增的序列图quot;劝OnClosingquot;

回答

评论会员:a 时间:2