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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

最近在做一个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的依赖,这也充分体现了面向结构变成的原则。

image

二、模拟简单的MVP

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

image

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

image

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显示出来,就像这样:

image

 

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


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
4月前
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
220 6
|
3月前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
216 3
|
3月前
|
vr&ar 图形学 API
Unity与VR控制器交互全解:从基础配置到力反馈应用,多角度提升虚拟现实游戏的真实感与沉浸体验大揭秘
【8月更文挑战第31天】虚拟现实(VR)技术迅猛发展,Unity作为主流游戏开发引擎,支持多种VR硬件并提供丰富的API,尤其在VR控制器交互设计上具备高度灵活性。本文详细介绍了如何在Unity中配置VR支持、设置控制器、实现按钮交互及力反馈,结合碰撞检测和物理引擎提升真实感,助力开发者创造沉浸式体验。
193 0
|
3月前
|
图形学 开发者
透视与正交之外的奇妙视界:深入解析Unity游戏开发中的相机与视角控制艺术,探索打造沉浸式玩家体验的奥秘与技巧
【8月更文挑战第31天】在Unity中,相机不仅是玩家观察游戏世界的窗口,更是塑造氛围和引导注意力的关键工具。通过灵活运用相机系统,开发者能大幅提升游戏的艺术表现力和沉浸感。本文将探讨如何实现多种相机控制,包括第三人称跟随和第一人称视角,并提供实用代码示例。
167 0
|
3月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
160 0
|
3月前
|
图形学 开发者 UED
Unity游戏开发必备技巧:深度解析事件系统运用之道,从生命周期回调到自定义事件,打造高效逻辑与流畅交互的全方位指南
【8月更文挑战第31天】在游戏开发中,事件系统是连接游戏逻辑与用户交互的关键。Unity提供了多种机制处理事件,如MonoBehaviour生命周期回调、事件系统组件及自定义事件。本文介绍如何有效利用这些机制,包括创建自定义事件和使用Unity内置事件系统提升游戏体验。通过合理安排代码执行时机,如在Awake、Start等方法中初始化组件,以及使用委托和事件处理复杂逻辑,可以使游戏更加高效且逻辑清晰。掌握这些技巧有助于开发者更好地应对游戏开发挑战。
160 0
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
71 2
|
2月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
76 0
|
2月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
62 0
|
2月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
66 0

推荐镜像

更多
下一篇
DataWorks