Enterprise Library深入解析与灵活应用(3):倘若将Unity、PIAB、Exception Handling引入MVP模式.. .. ..

最近在做一个Smart Client Software Factory的项目。熟悉SCSF或者CAB的都应该很清楚MVP这种设计模式。MVP是MVC的一种变体,View和Mode分别关注于UI的呈现和业务模型,View和Mode完全分离,View通过Presenter实现对业务模型的访问,Presenter“间接”地调用View实现对UI的操作。对于MVP中的异常处理,我们是直接通过Enterprise Library的Exception Handling Application Block来实现的。具体的做法是:在View中的每个控件的事件中添加try/catch block, 并在catch中通过ExceptionPolicy实现对异常的处理。这样导致的问题是,相同的代码重复散布于整个应用的各个角落,所以我又这样的想法:通过Policy Injection以AOP的方式实现对异常的处理,当有了这个想法后,我又多想了一步,何不再将Unity也一并整合进来。(Source Code 下载)

一、MVP简介

为了让一些没有接触过MVP的读者能够理解后续的内容,我先对MVP做一个简单的介绍。如下图所示:MVP有点类似于我们熟悉的MVC, View负责实现对UI的呈现已经与用户进行交互,在CAB中,View一般通过一个User Control来实现, Mode关注于具体的业务模型,独立于View和Presenter。View具有一个Presenter的引用,当View需要调用Mode的时候(比如需要访问Mode传入查询条件获取数据),通过Presenter访问Mode。对于Presenter来说,它需要对View进行操作(比如数据成功获取后,将其显示到View中),但是Presenter并不会“直接”对View本身进行引用,而是引用View的接口(IView),所以View对象是一个不稳定的对象,而Presenter仅仅需要View中一些固定的操作,所以对将这些操作定义在IView interface中,将对View的依赖转化成对IView的依赖,这也充分体现了面向结构变成的原则。

二、模拟简单的MVP

接下来我们通过一个简单的场景来模拟MVP。这是我常用的计算器的例子,整体的构成如下图所示:

1、ICalculator:Calculator的接口,定义了一个用于进行除法运算的操作

namespace Artech.UnityInMVP
 {
     public interface ICalculator
     {
         int Divide(int op1, int op2);
     }
 }  

2、Calculator:实现了ICalculator接口

namespace Artech.UnityInMVP
 {
     public class Calculator:ICalculator
     {
         public  int Divide(int op1, int op2)
         {
             return op1 / op2;
         }
     }
 }  

3、CalculatePresenter:被View调用进行数学运算,并将运算结果显示到View中

namespace Artech.UnityInMVP
 {
      public class CalculatePresenter
     {
         public CalculatePresenter()
         {
 this.Calculator = new Calculator();
         }  
        public ICalculateView View
         { get; set; }  
        public ICalculator Calculator
         { get; set; }  
        public void Calculate(int op1, int op2)
         {
             int result = this.Calculator.Divide(op1, op2);
             this.View.DisplayResult(result);
         }
     }
 }  

在CalculatePresenter具有一个ICalculateView 属性,通过该属性实现对于运算结果的显示。之所以定义ICalculator 是想解除对具体的Calculator的依赖,但是到目前为止,这个目标还没有达到,因为在构造函数中还是依赖于Calculator。

4、ICalculateView :定义了一个用于显示运算结果的操作,该操作被CalculatePresenter调用

namespace Artech.UnityInMVP
 {
    public interface ICalculateView
     {
         void DisplayResult(int result);
     }
 }  

5、CalculateView:在本例中是一个Form,并实现了ICalculateView

namespace Artech.UnityInMVP
 {
     public partial class CalculateView : ICalculateView
     {        
        public CalculatePresenter Presenter
         { get; set; }  
        #region ICalculateView Members  
        public void DisplayResult(int result)
         {
             this.textBoxResult.Text = result.ToString();
         }  
        #endregion  
        private void buttonCalculate_Click(object sender, EventArgs e)
         {
             int op1;
             int op2;
             if(!int.TryParse(this.textBoxOp1.Text.Trim(), out op1))
             {
                 return;
             }  
             if(!int.TryParse(this.textBoxOp2.Text.Trim(), out op2))
             {
                 return;
             }  
 try
              {
                  this.Presenter.Calculate(op1, op2);
    }
    catch (Exception ex)
              {
                  if (ExceptionPolicy.HandleException(ex, "UI Exception Policy"))
                  {
                      throw;
                  }
              }
         }  
        private void CalculateView_Load(object sender, EventArgs e)
         {
             this.Presenter = new CalculatePresenter ();
             this.Presenter.View = this;
         }
     }
 }  

在Load的时候对Presenter属性进行初始化, 并将View对象设置为View本身。在buttonCalculate_Click中,传入用户输入的操作数,并调用Presenter的Calculate方法。为了处理潜在的Exception,加了一个try/catch,并在catch中调用了Enterprise Library Excepton Handling Applicaion Block进行异常的处理。同时CalculateView 实现了ICalculateView的DisplayResult方法,将运算结果显示在TextBox中。

三、通过Unity和Policy Injection对上面的程序进行改造

我现在的目标是对上面的设计进行改进,达到下述两个目标:

  • 通过AOP的方式进行异常的处理,相同的try/catch频繁出现不是一个好的现象(实际上在我们现在的项目中,除了恶异常处理,还有其他一些相识的非业务逻辑,我希望的是这些业务无关的逻辑都通过AOP实现)。
  • 解除CalculatePresenter 对Calculator的依赖,使其仅仅依赖于ICalculator。

我的思路是这样的,将Policy Injection Application Block引入,用于实现Exception Handling操作;将Unity引入通过Depedency Injection实现对CalculatePresenter 和Calculator的解耦;同时通过Unity Extension实现Policy Injection和Unity的集成(参见本系列第一章).

为此我们先对CalculatePresenter进行改造。

namespace Artech.UnityInMVP
 {
 [ExceptionCallHandler("UI Exception Policy")]
     public class CalculatePresenter:MarshalByRefObject
     {
         public CalculatePresenter()
         {
             this.Calculator = new Calculator();
         }  
        public ICalculateView View
         { get; set; }  
 [Dependency]
         public ICalculator Calculator
         { get; set; }  
        public void Calculate(int op1, int op2)
         {
             int result = this.Calculator.Divide(op1, op2);
             this.View.DisplayResult(result);
         }
     }
 } 
  • 为了让Policy Injection能够起作用,我让其继承MarshalByRefObject,并且以Custom Attribute的形式应用了ExceptionCallHandler,并制定exception handling policy(在真正的项目开发中,我推荐通过configuration的方式应用Policy injection)。
  • 通过[Dependency]实现了基于Unity的Property dependency.

然后我们接着对View进行改造,由于我们在CalculatePresenter使用了[Dependency]和[ExceptionCallHandler],我们需要通过Unity Container的方式来创建CalculatePresenter对象,为此我定义了View的基类:ViewBase.

namespace Artech.UnityInMVP
 {
     public partial class ViewBase : Form
     {       
        private IUnityContainer _unityContainer;  
        protected IUnityContainer UnityContainer
         {
             get
             {
                 if (this._unityContainer == null)
                 {
                     this._unityContainer = new UnityContainer();
                     UnityConfigurationSection unityConfigSection = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
                     unityConfigSection.Containers.Default.Configure(this._unityContainer);
                 }
                 return this._unityContainer;
             }
         }
     }
 } 

在ViewBase 定义了IUnityContainer 属性,用于View创建对应的Presenter对象,这样CalculateView就可以这样来定义了:

namespace Artech.UnityInMVP
 {
     public partial class CalculateView : ViewBase, ICalculateView
     {
         public CalculateView()
         {
             InitializeComponent();                    
         }  
        public CalculatePresenter Presenter
         { get; set; }  
        #region ICalculateView Members  
        public void DisplayResult(int result)
         {
             this.textBoxResult.Text = result.ToString();
         }  
        #endregion  
        private void buttonCalculate_Click(object sender, EventArgs e)
         {
             int op1;
             int op2;
             if(!int.TryParse(this.textBoxOp1.Text.Trim(), out op1))
             {
                 return;
             }  
             if(!int.TryParse(this.textBoxOp2.Text.Trim(), out op2))
             {
                 return;
             }  
 this.Presenter.Calculate(op1, op2);
         }  
        private void CalculateView_Load(object sender, EventArgs e)
         {
             this.Presenter = this.UnityContainer.Resolve<CalculatePresenter>();
             this.Presenter.View = this;
         }
     }
 } 

在buttonCalculate_Click中,根本就不需要try/catch了,在View初始化时,直接通过UnityContainer的Resolve方法创建Presenter。

我们最后来看看相关的配置:

<?xml version="1.0" encoding="utf-8"?>
 <configuration>
   <configSections>
     <section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
     <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
   </configSections>
   <exceptionHandling>
     <exceptionPolicies>
       <add name="UI Exception Policy">
         <exceptionTypes>
           <add type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
             postHandlingAction="None" name="Exception">
             <exceptionHandlers>
               <add type="Artech.UnityInMVP.ExceptionHandlers.MessageBoxHandler,Artech.UnityInMVP"
                 name="Custom Handler" />
             </exceptionHandlers>
           </add>
         </exceptionTypes>
       </add>
     </exceptionPolicies>
   </exceptionHandling>
   <unity>
     <containers>
       <container>
         <types>
             <type type=" Artech.UnityInMVP.ICalculator,Artech.UnityInMVP" mapTo="Artech.UnityInMVP.Calculator,Artech.UnityInMVP"/>
         </types>
         <extensions>
           <add type="Artech.UnityInMVP.UnityExtensions.PolicyInjectionExtension,Artech.UnityInMVP" />
         </extensions>
       </container>
     </containers>
   </unity>
 </configuration> 

其中第一部分是exceptionHandling的配置,为了简单起见,我创建了一个自定义的ExceptionHandler:MessageBoxHandler来处理所有的exception,该handler仅仅将error message通过MessageBox显示出来,有兴趣的朋友可以下载source code看看。

<exceptionHandling>
     <exceptionPolicies>
       <add name="UI Exception Policy">
         <exceptionTypes>
           <add type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
             postHandlingAction="None" name="Exception">
             <exceptionHandlers>
               <add type="Artech.UnityInMVP.ExceptionHandlers.MessageBoxHandler,Artech.UnityInMVP"
                 name="Custom Handler" />
             </exceptionHandlers>
           </add>
         </exceptionTypes>
       </add>
     </exceptionPolicies>
   </exceptionHandling>

第二部分是unity的配置,在<types>中定义了ICalculator和Calculator的mapping关系,实现了Presenter和Calculator的解耦;而extensions的配置实现了Policy Injection和Unity的集成,详细实现可以查看本系列第一章。

 <unity>
     <containers>
       <container>
         <types>
             <type type=" Artech.UnityInMVP.ICalculator,Artech.UnityInMVP" mapTo="Artech.UnityInMVP.Calculator,Artech.UnityInMVP"/>
         </types>
         <extensions>
           <add type="Artech.UnityInMVP.UnityExtensions.PolicyInjectionExtension,Artech.UnityInMVP" />
         </extensions>
       </container>
     </containers>
   </unity>
 

这就使所有的实现。如何运算出现异常,比如将第二个操作数设为零,我们定义的MessageBoxHandler就会被执行,并通过MessageBox将Message显示出来,就像这样:

P.S. 虽然讲Policy Injection应用到Presenter可以通过AOP的方式来进行异常的处理,但是这要求View上的所有具有潜在异常抛出的逻辑都需要通过Presenter来实现,因为ExceptionHandler是应用到Presenter上面的。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏木宛城主

Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 1)

初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Comm...

40060
来自专栏.NET开发那点事

IoC原理-使用反射/Emit来实现一个最简单的IoC容器

从Unity到Spring.Net,到Ninject,几年来陆陆续续用过几个IoC框架。虽然会用,但也没有一直仔细的研究过IoC实现的过程。最近花了点时间,下了...

253100
来自专栏木宛城主

Unity应用架构设计(8)——使用ServiceLocator实现对象的注入

对象的 『注入』 是企业级软件开发经常听到的术语。如果你是一个 Java 程序员,一定对注入有着深刻的映像。不管是SSH框架还是SSM框架,Spring 全家...

25280
来自专栏木宛城主

Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 2)

MVVM回顾 经过上一篇文章的介绍,相信你对MVVM的设计思想有所了解。MVVM的核心思想就是解耦,View与ViewModel应该感受不到彼此的存在。 V...

51290
来自专栏施炯的IoT开发专栏

2015 MVP OpenDay 及 Community Camp 演讲PPT分享

这两天来到首都北京参加一年一度的MVP OpenDay 和 MVP Community Camp。其中,31号下午有我的课程:What Will You Mak...

21060
来自专栏木宛城主

Unity应用架构设计(13)——日志组件的实施

对于应用程序而言,日志是非常重要的功能,通过日志,我们可以跟踪应用程序的数据状态,记录Crash的日志可以帮助我们分析应用程序崩溃的原因,我们甚至可以通过日志...

23750
来自专栏木宛城主

Unity应用架构设计(9)——构建统一的 Repository

谈到 『Repository』 仓储模式,第一映像就是封装了对数据的访问和持久化。Repository 模式的理念核心是定义了一个规范,即接口『Interfa...

24360
来自专栏木宛城主

Unity应用架构设计(5)——ViewModel之间如何共享数据

对于客户端应用程序而言,单页应用程序(Single Page Application)是最常见的表现形式。有经验的开发人员往往会把一个View分解多个SubV...

23460
来自专栏Fish

安装IDEA和运行SCALA程序

下载与配置IDEA 从官网下载 里面有Ultimate(最终版)和Community(社区版),对于普通的开发者来说,社区版就够了,然后因为我本来配置了JDK...

55590
来自专栏木宛城主

Unity应用架构设计(2)——使用中介者模式解耦ViewModel之间通信

当你开发一个客户端应用程序的时候,往往一个单页会包含很多子模块,在不同的平台下,这些子模块又被叫成子View(视图),或者子Component(组件)。越是复...

325100

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励