[翻译-ASP.NET MVC]Contact Manager开发之旅迭代4 - 利用设计模式松散耦合

简介:
本翻译系列为 asp.net mvc官方实例教程。在这个系列中,Stephen Walther将演示如何通过ASP.NET MVC framework结合单元测试、TDD、Ajax、软件设计原则及设计模式创建一个完整的Contact Manager应用。本系列共七个章节,也是七次迭代过程。本人将陆续对其进行翻译并发布出来,希望能对学习ASP.NET MVC 的各位有所帮助。由于本人也是个MVC菜鸟,且E文水平亦是平平,文中如有疏漏敬请见谅。
注:为保证可读性,文中Controller、View、Model、Route、Action等ASP.NET MVC核心单词均未翻译。

 

ContactManager开发之旅-索引页

ContactManager开发之旅 迭代1 - 创建应用程序

ContactManager开发之旅 迭代2 - 修改样式,美化应用

ContactManager开发之旅 迭代3 - 验证表单

迭代4 利用设计模式松散耦合

本次迭代

这是ContactManager的第四次迭代,本次迭代中我们将重构应用程序,通过合理的利用设计模式松散其耦合。松耦合的程序更有弹性,更易维护。当应用程序面临改动时,你只需修改某一部分的代码,而不会出现大量修改与其耦合严重的相关代码这种牵一发而动全身的情况。

在当前的ContactManager应用中,所有的数据存储及验证逻辑都分布在controller类中,这并不是个好主意。要知道这种情况下一旦你需要修改其中一部分代码,你将同时面临为其他部分增加bug的风险。比如你需要修改验证逻辑,你就必须承担数据存储或controller部分的逻辑会随之出现问题的风险。

(SRP-单一职责原则), 就一个类而言,应该只专注于做一件事和仅有一个引起它变化的原因。将controller、验证及数据存储逻辑耦合在一起严重的违反了SRP。

需求的变更,个人想法的升华、始料未及的情况如此频繁,你不得不就当前的应用作出一些改变:为应用程序添加新功能、修复bug、修改应用中某个功能的实现等。就应用程序而言,它们很难处于一种静止不动的状态,他们无时无刻在被不停的改变、改善。

现在的情况是,ContactManager应用中使用了Microsoft Entity Framework处理数据通信。想象一下,你决定对数据存储层实现做出一些改变,你希望使用其它的一些方案:如ADP.NET Data Services或NHibernate。由于数据存储相关的代码并不独立于验证及controller中的代码,你将无法避免修改那些原本应该毫无干系的代码。

而另一方面,对于一个松耦合的程序来说,上面的问题就不存在了。一个经典的场景是:你可以随意切换数据存储方案而不用管验证逻辑、controller中的那些劳什子代码。

在这次迭代中,我们将利用软件设计模式的优点重构我们的Contact Manager应用程序,使其符合我们上面提到的“松耦合”的要求。尽管做完这些以后,我们的应用程序并不会表现的与以往有任何不同,但是我们从此便可轻松驾驭其未来的维护及修改过程。

重构就是指在不改变程序外在行为的前提下,对代码做出修改,改进程序内部结构的过程。

 

使用Repository模式

我们的第一个改动便是使用叫做Repository的设计模式改善我们的应用。我们将使用这个模式将数据存储相关的代码与我们应用中的其他逻辑独立开来。

要实现Repository模式,我们需要做以下两件事

  1. 新建一个接口
  2. 新建一个类实现上面的接口。

首先,我们需要新建一个接口约定所有我们需要实现的数据存储方法。IContactManagerRepository接口代码如下。这个接口约定了五个方法:CreateContact()、DeleteContact()、EditContact()、GetContact()及ListContacts()方法:

using System;
using System.Collections.Generic;

namespace ContactManager.Models
{
    public interface IContactRepository
    {
        Contact CreateContact(Contact contactToCreate);
        void DeleteContact(Contact contactToDelete);
        Contact EditContact(Contact contactToUpdate);
        Contact GetContact(int id);
        IEnumerable<Contact> ListContacts();
    }
}

接着,我们需要新建一个具体的类来实现IContactManagerRepositoyr接口。由于我们这里使用Microsoft Entity Framework操作数据库,所以我们为这个类命名为“EntityContactManagerRepository”,这个类的代码如下:

using System.Collections.Generic;
using System.Linq;

namespace ContactManager.Models
{
    public class EntityContactManagerRepository : ContactManager.Models.IContactManagerRepository
    {
        private ContactManagerDBEntities _entities = new ContactManagerDBEntities();

        public Contact GetContact(int id)
        {
            return (from c in _entities.ContactSet
                    where c.Id == id
                    select c).FirstOrDefault();
        }


        public IEnumerable<Contact> ListContacts()
        {
            return _entities.ContactSet.ToList();
        }


        public Contact CreateContact(Contact contactToCreate)
        {
            _entities.AddToContactSet(contactToCreate);
            _entities.SaveChanges();
            return contactToCreate;
        }


        public Contact EditContact(Contact contactToEdit)
        {
            var originalContact = GetContact(contactToEdit.Id);
            _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit);
            _entities.SaveChanges();
            return contactToEdit;
        }


        public void DeleteContact(Contact contactToDelete)
        {
            var originalContact = GetContact(contactToDelete.Id);
            _entities.DeleteObject(originalContact);
            _entities.SaveChanges();
        }

    }
}

注意,EntityContactManagerRepository类实现了IContactManagerRepository接口约定的5个方法。

为什么我们一定要要建立个接口再建立一个类来实现它呢?

应用程序中的其他部分将与接口而不是具体的类进行交互。也就是说,它们将调用接口声明方法而不是具体的类中的方法。

所以,我们可以以一个新的类实现某个接口但不用修改应用程序中其他的部分。例如,将来我们可能需要建立一个DataServicesContactManagerRepository类实现IContactManagerRepository接口。DataServicesContactManagerRepository类使用ADO.NET Data Services,我们用它代替Microsoft Entity Framework.与数据库通信进行数据存储。

如果我们的应用程序代码是基于IContactManagerRepository接口而不是EntityContactManagerRepository这个具体的类,那么我们可以只改变不同的类名而非代码中的其他部分。例如我们可以将EntityContactManagerRepository修改成DataServicesContactManagerRepository而不用去碰数据存储和验证逻辑相关的代码。

面向接口(虚类)编程使我们的应用程序更有弹性,更易修改。

通过在VS中选择“重构”菜单->“提取接口”,你可以根据一个具体的类方便快速的创建出一个与之对应的接口。例如你可以先建立一个EntityContactManagerRepository类,然后使用如上文所述的方法自动生成IContactManagerRepository接口。

使用依赖注入

现在,我们已经将数据访问相关的代码独立到了Repository类中。而后,我们需要修改Contact controller以适应这些改变。这里我们将使用依赖注入的方式。

修改后的Contact controller代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using ContactManager.Models;

namespace ContactManager.Controllers
{
    public class ContactController : Controller
    {
        private IContactManagerRepository _repository;
        public ContactController()
            : this(new EntityContactManagerRepository()) { }

        public ContactController(IContactManagerRepository repository)
        {
            _repository = repository;
        }

        protected void ValidateContact(Contact contactToValidate)
        {
            if (contactToValidate.FirstName.Trim().Length == 0)
                ModelState.AddModelError("FirstName", "First name is required.");
            if (contactToValidate.LastName.Trim().Length == 0)
                ModelState.AddModelError("LastName", "Last name is required.");
            if (contactToValidate.Phone.Length > 0 && !Regex.IsMatch(contactToValidate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
                ModelState.AddModelError("Phone", "Invalid phone number.");
            if (contactToValidate.Email.Length > 0 && !Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
                ModelState.AddModelError("Email", "Invalid email address.");
        }

        //
        // GET: /Home/
        public ActionResult Index()
        {
            return View(_repository.ListContacts());
        }

        //
        // GET: /Home/Details/5

        public ActionResult Details(int id)
        {
            return View();
        }

        //
        // GET: /Home/Create

        public ActionResult Create()
        {
            return View();
        }

        //
        // POST: /Home/Create

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
        {
            //Validation logic

            ValidateContact(contactToCreate);

            if (!ModelState.IsValid)
            {
                return View();
            }
            else
            {
                try
                {
                    _repository.CreateContact(contactToCreate);
                    return RedirectToAction("Index");
                }
                catch
                {
                    return View();
                }
            }
        }

        //
        // GET: /Home/Edit/5

        public ActionResult Edit(int id)
        {
            return View(_repository.GetContact(id));
        }

        //
        // POST: /Home/Edit/5

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(Contact contactToEdit)
        {

            ValidateContact(contactToEdit);
            if (!ModelState.IsValid)
                return View();
            try
            {
                _repository.EditContact(contactToEdit);
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        //
        // GET: /Home/Delete/5
        public ActionResult Delete(int id)
        {
            return View(_repository.GetContact(id));
        }

        //
        // POST: /Home/Delete/5
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Delete(Contact contactToDelete)
        {
            try
            {
                _repository.DeleteContact(contactToDelete);
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }
    }
}

注意上面代码中,Contact controller包含两个构造函数。第一个构造函数向第二个构造函数传递一个基于IContactManagerRepository接口的实例。这就是“构造子注入”。

EntityContactManagerRepository类仅仅在第一个构造函数中被使用。其他的地方一律使用IContactManagerRepository接口代替确切的EntityContactManagerRepository类。

在这种情况下,如果以后我们想改变IContactManagerRepository的实现也就很方便了。比如你想使用DataServicesContactRepository类代替EntityContactManagerRepository类,则只需要修改第一个构造函数即可。

不仅如此,构造子注入更使Contact controller的可测试性变得更强。在你的单元测试用,你可以通过传递一个mock的IContactManagerRepository的实现进而实例化Contact controller。依赖注入所带来的特性将在我们对Contact Manager的下一次迭代—进行单元测试—时显得非常重要。

如果你希望将Contact controller类与具体的IContactManagerRepository接口的实现彻底解耦,则可以使用一些支持依赖注入的框架,如StructureMap或Microsoft Entity Framework (MEF)。有了这些依赖注入框架的帮忙,你就不必在代码中面向具体的类了。

建立service层

你应该注意到了,我们的验证逻辑仍与上面代码中修改过的controller逻辑混合在一起。像我们独立数据存储逻辑一样,将验证逻辑独立出来同样是个好注意。

So,我们应当建立service层。在这里,它作为独立的一层以衔接controller和repository类。service层应当包括所有的业务逻辑,我们的验证逻辑当然也不例外。

ContactManagerService的代码如下,我们将验证逻辑转移到了这里:

using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using ContactManager.Models.Validation;

namespace ContactManager.Models
{
    public class ContactManagerService : IContactManagerService
    {
        private IValidationDictionary _validationDictionary;
        private IContactManagerRepository _repository;


        public ContactManagerService(IValidationDictionary validationDictionary)
            : this(validationDictionary, new EntityContactManagerRepository())
        { }


        public ContactManagerService(IValidationDictionary validationDictionary, IContactManagerRepository repository)
        {
            _validationDictionary = validationDictionary;
            _repository = repository;
        }


        public bool ValidateContact(Contact contactToValidate)
        {
            if (contactToValidate.FirstName.Trim().Length == 0)
                _validationDictionary.AddError("FirstName", "First name is required.");
            if (contactToValidate.LastName.Trim().Length == 0)
                _validationDictionary.AddError("LastName", "Last name is required.");
            if (contactToValidate.Phone.Length > 0 && !Regex.IsMatch(contactToValidate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
                _validationDictionary.AddError("Phone", "Invalid phone number.");
            if (contactToValidate.Email.Length > 0 && !Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
                _validationDictionary.AddError("Email", "Invalid email address.");
            return _validationDictionary.IsValid;
        }


        #region IContactManagerService Members

        public bool CreateContact(Contact contactToCreate)
        {
            // Validation logic
            if (!ValidateContact(contactToCreate))
                return false;

            // Database logic
            try
            {
                _repository.CreateContact(contactToCreate);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public bool EditContact(Contact contactToEdit)
        {
            // Validation logic
            if (!ValidateContact(contactToEdit))
                return false;

            // Database logic
            try
            {
                _repository.EditContact(contactToEdit);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public bool DeleteContact(Contact contactToDelete)
        {
            try
            {
                _repository.DeleteContact(contactToDelete);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public Contact GetContact(int id)
        {
            return _repository.GetContact(id);
        }

        public IEnumerable<Contact> ListContacts()
        {
            return _repository.ListContacts();
        }

        #endregion
    }
}

需要注意的是,ContactManagerService的构造函数中需要一个ValidationDictionary参数。service层通过这个ValidationDictionary与controller层进行交互。我们将在接下来讨论装饰者模式时来说明它。

更值得注意的是,ContactManagerService实现了IContactManagerService接口。你需要时刻努力进行面向接口变成。Contact Manager应用中的其他类都不与具体的ContactManagerService类直接交互。它们皆需面向IContactManagerService接口。

IContactManagerService接口的代码如下:

 

 

using System.Collections.Generic; namespace ContactManager.Models { public interface IContactManagerService { bool CreateContact(Contact contactToCreate); bool DeleteContact(Contact contactToDelete); bool EditContact(Contact contactToEdit); Contact GetContact(int id); IEnumerable<Contact> ListContacts(); } }

修改后的Contact controller类代码如下,这里Contact controller类已不再与ContactManager service交互,每一层都尽可能的与其他层独立开来。

using System.Web.Mvc;
using ContactManager.Models;
using ContactManager.Models.Validation;

namespace ContactManager.Controllers
{
    public class ContactController : Controller
    {
        private IContactManagerService _service;

        public ContactController()
        {
            _service = new ContactManagerService(new ModelStateWrapper(this.ModelState));

        }

        public ContactController(IContactManagerService service)
        {
            _service = service;
        }

        public ActionResult Index()
        {
            return View(_service.ListContacts());
        }

        public ActionResult Create()
        {
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
        {
            if (_service.CreateContact(contactToCreate))
                return RedirectToAction("Index");
            return View();
        }

        public ActionResult Edit(int id)
        {
            return View(_service.GetContact(id));
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(Contact contactToEdit)
        {
            if (_service.EditContact(contactToEdit))
                return RedirectToAction("Index");
            return View();
        }

        public ActionResult Delete(int id)
        {
            return View(_service.GetContact(id));
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Delete(Contact contactToDelete)
        {
            if (_service.DeleteContact(contactToDelete))
                return RedirectToAction("Index");
            return View();
        }

    }
}

我们的应用程序已经不再违反SRP原则了。上面所示代码的Contact controller中,所有的验证逻辑都被转移到service层中,所有的数据库存储逻辑都被转移到repository层中。

使用装饰者模式

我们欲将service层与controller层完全解耦,原则上讲也就是我们应当可以在独立的程序集中编译service层而无需添加对MVC应用程序的引用。

然而我们的service层需要将验证错误信息回传给controller层,那么我们如何才能在service层和controller不耦合的前提下完成这项任务呢?答案是:装饰者模式。

Contrlooer使用名为ModelState的ModelStateDictionary表现验证错误信息。因此我们可能会想将ModelState从controller层传递到sercice层。然而在service层中使用ModelState会使你的服务层依赖于ASP.NET MVC framework提供的某些特性。这可能会很糟,假设某天你想在一个WPF应用程序中使用这个service层,你就不得不添加对ASP.NET MVC framework的引用才能使用ModelStateDictionary类。

装饰者模式通过将已有的类包装在新的类中从而实现某接口。我们的Contact Manager项目中包含的ModelStateWrapper类的代码如下:

using System.Web.Mvc;

namespace ContactManager.Models.Validation
{
    public class ModelStateWrapper : IValidationDictionary
    {
        private ModelStateDictionary _modelState;

        public ModelStateWrapper(ModelStateDictionary modelState)
        {
            _modelState = modelState;
        }

        public void AddError(string key, string errorMessage)
        {
            _modelState.AddModelError(key, errorMessage);
        }

        public bool IsValid
        {
            get { return _modelState.IsValid; }
        }
    }
}

其接口代码如下:

namespace ContactManager.Models.Validation
{
    public interface IValidationDictionary
    {
        void AddError(string key, string errorMessage);
        bool IsValid { get; }
    }
}

仔细观察IContactManagerService接口中的代码,你会发现ContactManager service层中仅使用了IValidationDictionary接口。ContactManager service不依赖ModelStateDictionary类。当Contact controller创建ContactManager service时,controller将其ModelState包装成如下的样子:

_service = new ContactManagerService(new ModelStateWrapper(this.ModelState));

总结

本次迭代中,我们并没有对Contact Manager应用添加任何的功能。本次迭代的目的是通过重构应用程序,使Contact Manager更易维护、更易修改。

首先,我们实现了Repository软件设计模式。我们将所有的数据存取相关的代码提取到独立的ContactManager repository类中。同时,我们也将验证逻辑从controller逻辑中独立出来,将其放入我们另外创建的独立的service层中。controller层与service层交互,service层则与repository层交互。

然后我们通过装饰者模式将ModelState从service层中独立出来。在我们的service层中,我们只需针对IValidationDictionary接口进行编码,而非针对ModelState类。

最后,我们使用了依赖注入这种软件设计模式。该模式使得我们在开发中可以避开具体类,而针对接口(虚类)编码。得益于依赖注入模式,我们的代码变得更具测试性。在下一次迭代中,我们将向项目中添加单元测试。

















本文转自紫色永恒51CTO博客,原文链接: http://www.cnblogs.com/024hi/archive/2009/04/12/ASP_NET_MVC_SAMPLE_CONTACT_MANAGER_4.html,如需转载请自行联系原作者


相关文章
|
1月前
|
设计模式 开发框架 JavaScript
基于.NET8 + Vue/UniApp前后端分离的快速开发框架,开箱即用!
基于.NET8 + Vue/UniApp前后端分离的快速开发框架,开箱即用!
|
2月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
169 3
|
1月前
|
传感器 人工智能 供应链
.NET开发技术在数字化时代的创新作用,从高效的开发环境、强大的性能表现、丰富的库和框架资源等方面揭示了其关键优势。
本文深入探讨了.NET开发技术在数字化时代的创新作用,从高效的开发环境、强大的性能表现、丰富的库和框架资源等方面揭示了其关键优势。通过企业级应用、Web应用及移动应用的创新案例,展示了.NET在各领域的广泛应用和巨大潜力。展望未来,.NET将与新兴技术深度融合,拓展跨平台开发,推动云原生应用发展,持续创新。
35 4
|
1月前
|
机器学习/深度学习 人工智能 物联网
.NET 技术:引领未来开发潮流
.NET 技术以其跨平台兼容性、高效的开发体验、强大的性能表现和安全可靠的架构,成为引领未来开发潮流的重要力量。本文深入探讨了 .NET 的核心优势与特点,及其在企业级应用、移动开发、云计算、人工智能等领域的广泛应用,展示了其卓越的应用价值和未来发展前景。
61 5
|
1月前
|
存储 缓存 NoSQL
2款使用.NET开发的数据库系统
2款使用.NET开发的数据库系统
|
2月前
|
JSON C# 开发者
C#语言新特性深度剖析:提升你的.NET开发效率
【10月更文挑战第15天】C#语言凭借其强大的功能和易用性深受开发者喜爱。随着.NET平台的演进,C#不断引入新特性,如C# 7.0的模式匹配和C# 8.0的异步流,显著提升了开发效率和代码可维护性。本文将深入探讨这些新特性,助力开发者在.NET开发中更高效地利用它们。
44 1
|
1月前
|
开发框架 JavaScript 前端开发
2024年全面且功能强大的.NET快速开发框架推荐,效率提升利器!
2024年全面且功能强大的.NET快速开发框架推荐,效率提升利器!
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
3月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
1月前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###