中国人数下单不打折。依赖注入不是目的。

扫除因就是松耦合,松耦合就必定好啊?当然不是先后的耦合度与外聚度要在一起说,当然,对于世界层来说,适当的抽类似及类似中的依靠是甚有必不可少之,看下面我的立即段代码

近些年几乎龙在看一样依名也Dependency Injection in
.NET 的开,主要讲了什么是赖注入,使用依赖注入的长处,以及.NET平台及负注入的各种框架和用法。在这按照开的开始,讲述了软件工程中之一个最主要之眼光就是是关心分离(Separation
of
concern, SoC)。依赖注入不是目的,它是一样多样工具及手法,最终之目的是赞助我们出有松散耦合(loose
coupled)、可保障、可测试的代码和次序。这条标准的做法是豪门熟知的面向接口,或者说是面向抽象编程。

一个例子,一个订单系统,它根据国家产生例外的下单方法,对于低层(DATA层)是同一的,但对世界层,也吃工作逻辑层,它是勿平等的,可能略日本产仅仅从5相撞,中国人数下单不打折,我呢是可以理解的,因为中国石油在对于华口齐是怪抠门的,而对于有些日本则慷慨解囊。不多说了,看代码:

有关什么是依靠注入,在Stack
Overflow上面有一个题材,如何为一个5东的幼解释依赖注入,其中得分最高的一个答案是:

    /// <summary>

    /// 订单实体

    /// </summary>

    class Order

    {

 

    }

 

    #region 传统作法,无有考虑约束

    /// <summary>

    /// 针对美国人的订单

    /// </summary>

    class OrderUSA

    {

        public void Insert(Order order) { }

    }

    /// <summary>

    /// 什对日本人的订单

    /// </summary>

    class OrderJPN

    {

        public void Insert(Order order) { }

    }

    class OrderService

    {

        OrderUSA orderAction1 = new OrderUSA();

        public void Insert(Order order)

        {

            orderAction1.Insert(order);

        }

    }

    #endregion

 

    #region 解除具体依赖后的程序

    /// <summary>

    /// 和订单操作相关的接口

    /// </summary>

    public interface IOrderAction

    {

        void Insert(Order order);

        void Delete(Order order);

    }

    /// <summary>

    /// 针对中国人的订单

    /// </summary>

    public class OrderChina : IOrderAction

    {

        public void Insert(Order order)

        {

            throw new NotImplementedException();

        }

        public void Delete(Order order)

        {

            throw new NotImplementedException();

        }

    }

    public class OrderService2

    {

        private IOrderAction iOrderInsert;

        /// <summary>

        /// 根据所传递的接口类型的参数来确定使用哪种对象

        /// </summary>

        /// <param name="_iOrderInsert"></param>

        public OrderService2(IOrderAction _iOrderInsert)

        {

            this.iOrderInsert = _iOrderInsert;

        }

        public void InsertOrder(Order order)

        {

            this.iOrderInsert.Insert(order);

        }

        public void DeleteOrder(Order order)

        {

            this.iOrderInsert.Delete(order);

        }

    }

    #endregion

“When you go and get things out of the refrigerator for yourself, you
can cause problems. You might leave the door open, you might get
something Mommy or Daddy doesn’t want you to have. You might even be
looking for something we don’t even have or which has expired.

What you should be doing is stating a need, “I need something to drink
with lunch,” and then we will make sure you have something when you sit
down to eat.”

辉映到面向对象程序开发被便是:高层类(5载小儿)应该乘底层基础设备(家长)来提供必要的劳务。

编写松耦合的代码说起来十分简短,但是事实上写在写在就是改为了紧耦合。

采取例子来验证或又简洁,首先来看看哪些的代码是紧耦合。

1 不好的实现

编写松耦合代码的第一步,可能大家都耳熟能详,那就是是针对性系子。比如下面的经典的老三重合架构。

图片 1

分开完层和实现好是简单起事情,并无是说分好层之后就能松耦合了。

1.1 紧耦合的代码

来那个多种办法来规划一个活的,可保护的纷繁应用,但是n层架构是同一种植大家比较熟悉的主意,这其中的挑战在于安对的贯彻n层架构。

要是要贯彻一个挺粗略的电子商务网站,要列有商品列表,如下:

图片 2

脚就具体来演示通常的做法,是怎样一步一步将代码写有紧耦合的。

1.1.1 数据访问层

而兑现货物列表这无异于功力,首先使编制数据访问层,需要规划数据库及表,在SQLServer中统筹之数据库表Product结构如下:

图片 3

表明设计好后,就得开勾画代码了。在Visual Studio
中,新建一个叫做吧DataAccessLayer的工程,添加一个ADO.NET Entity Data
Model,此时Visual Studio的于导会自动帮助我们别Product实体和ObjectContext
DB操作及下文。这样咱们的 Data Access Layer就写好了。

图片 4

1.1.2 业务逻辑层

呈现层实际上可以直接看数访问层,通过ObjectContext 获取Product
列表。但是多数气象下,我们不是直拿DB里面的数据显现出,而是需要针对数码进行处理,比如对准会员,需要针对某些商品的价打折。这样咱们尽管待工作逻辑层,来处理这些和实际工作逻辑相关的政工。

新建一个类库,命名吧DomainLogic,然后上加一个称为也ProductService的接近:

public class ProductService {
    private readonly CommerceObjectContext objectContext;

    public ProductService()
    {
        this.objectContext = new CommerceObjectContext();
    }

    public IEnumerable<Product> GetFeaturedProducts(
        bool isCustomerPreferred)
    {
        var discount = isCustomerPreferred ? .95m : 1;
        var products = (from p in this.objectContext
                            .Products
                        where p.IsFeatured
                        select p).AsEnumerable();
        return from p in products
                select new Product
                {
                    ProductId = p.ProductId,
                    Name = p.Name,
                    Description = p.Description,
                    IsFeatured = p.IsFeatured,
                    UnitPrice = p.UnitPrice * discount
                };
    }
}

本我们的事务逻辑层已经落实了。

1.1.3 表现层

今日贯彻表现层逻辑,这里运用ASP.NET MVC,在Index
页面的Controller中,获取商品列表然后用数据返回给View。

public ViewResult Index()
{
    bool isPreferredCustomer = 
        this.User.IsInRole("PreferredCustomer");

    var service = new ProductService();
    var products = 
        service.GetFeaturedProducts(isPreferredCustomer);
    this.ViewData["Products"] = products;

    return this.View();
}

下一场在View中将Controller中回到的数量表现出:

<h2>Featured Products</h2>
<div>
<% var products =
        (IEnumerable<Product>)this.ViewData["Products"];
    foreach (var product in products)
    { %>
    <div>
    <%= this.Html.Encode(product.Name) %>
    (<%= this.Html.Encode(product.UnitPrice.ToString("C")) %>)
    </div>
<% } %>
</div>

1.2 分析

而今,按照三交汇“架构”我们的代码写好了,并且为达了要求。整个项目之构造使下图:

 图片 5

当时当是咱们普通经常形容的所谓的老三叠架构。在Visual
Studio中,三叠里的指可以经项目援表现出来。

1.2.1 依赖关系图

今咱们来分析一下,这三重叠里的依靠关系,很显著,上面的落实着,DomianLogic需要负SqlDataAccess,因为DomainLogic中之所以到了Product这同样实体,而此实体是概念在DataAccess这等同重叠的。WebUI这等同重叠需要靠DomainLogic,因为ProductService在这无异于层,同时,还需依赖DataAccess,因为当UI中为使了Product实体,现在任何系统的依赖关系是这么的:

图片 6

1.2.2 耦合性分析

应用三叠组织的显要目的是分手关注点,当然还有一个缘故是不过测试性。我们应以世界模型从数看层与显现层中分别出来,这样马上片个层的成形才不见面传染领域模型。在好之系中,这点非常关键,这样才能够拿系统遭到之异部分隔断开来。

今天来拘禁之前的落实着,有没产生模块性,有没有发出死模块可凝集出也。现在丰富几只新的case来拘禁,系统是否能够响应这些需求:

补充加新的用户界面

除却WebForm用户之外,可能还需要一个WinForm的界面,现在咱们可否复用领域层及数量访问层呢?从依赖图被得以视,没有另外一个模块会借助表现层,因此特别易实现就或多或少转移。我们唯有需要创造一个WPF的富客户端就可以。现在漫天系统的乘图如下:

图片 7

变新的数据源

或许了了一段时间,需要拿所有系统安排及讲话及,要运用外的数据存储技术,比如Azure
Table Storage Service。现在,整个访问数的协议来了变更,访问Azure
Table Storage Service的章程是Http协议,而之前的大多数.NET
访问数的方都是因ADO.NET
的法。并且数据源的保留方法啊时有发生了改变,之前是涉嫌项目数据库,现在改成了key-value型数据库。

图片 8 

出于方的负关系图可以见到,所有的层都依赖了数额访问层,如果改动数据访问层,则领域逻辑层,和呈现层都亟待展开相应的修改。

1.2.3 问题

除却上面的各层之间耦合下喽大之外,代码中尚产生外题目。

  • 领域模型似乎还写及了数看层中。所以世界模型看起依赖了数额访问层。在数额看层中定义了名吧Product的好像,这种看似应该是属世界模型层的。
  • 见层中掺入了决定某用户是否是会员的逻辑。这种业务逻辑应该是
    业务逻辑层中应有处理的,所以也应有坐世界模型层
  • ProductService因为据了数码访问层,所以呢会拄在web.config
    中安排的数据库连接字符串等消息。这使,整个事情逻辑层也得靠这些部署才能够正常运转。
  • 每当View中,包含了最为多矣函数性功能。他履行了要挟类型转换,字符串格式化等操作,这些意义应该是以界面显示得模型中成就。

方可能是咱大部分勾代码时候的兑现,
UI界面层去因了数量访问层,有时候偷懒就直引用了立即同样重叠,因为实体定义在其间了。业务逻辑层也是凭数据访问层,直接当业务逻辑中用了多少访问层里面的实业。这样叫所有系统紧耦合,并且只是测试性差。那本咱们省,如何改这样一个体系,使的达到松散耦合,从而增强而测试性呢?

2 较好的实现

借助注入能够比好之解决点出现的题目,现在可下这无异于想想来重新实现前面的系统。之所以又实现是坐,前面的贯彻在平初始的像就是从来不考虑到扩展性和松耦合,使用重构的法子很麻烦上良好之效应。对于小的体系的话可能还好,但是对一个特大型的网,应该是比困难的。

在描绘代码的时光,要管理好因,在前边的落实这种,代码直接控制了赖:当ProductService需要一个ObjectContext类的若,直接new了一个,当HomeController需要一个ProductService的时候,直接new了一个,这样看起老酷很方便,实际上使得所有系统具有十分可怜的局限性,变得紧耦合。new
操作实际就是引入了指,
控制反转这种思考便只要使之我们于好的军事管制依赖。

2.1 松耦合的代码

2.1.1 表现层

先是由展现层来分析,表现层重要是故来对数据开展展现,不该包含了多之逻辑。在Index的View页面中,代码希望可以形容成这么

<h2>
    Featured Products</h2>
<div>
    <% foreach (var product in this.Model.Products)
        { %>
    <div>
        <%= this.Html.Encode(product.SummaryText) %></div>
    <% } %>
</div>

可看看,跟之前的显现层代码相比,要净化多。很明白是不待进行类型转换,要兑现如此的目的,只需要为Index.aspx这个视图继承自
System.Web.Mvc.ViewPage<FeaturedProductsViewModel>
即可,当我们当起Controller创建View的当儿,可以拓展精选,然后会自动生成。整个用于展示的消息在了SummaryText字段受到。

这里就是引入了一个视图模型(View-Specific
Models),他裹了视图的行,这些模型只是简单的POCOs对象(Plain Old CLR
Objects)。FeatureProductsViewModel中寓了一个List列表,每个元素是一个ProductViewModel类,其中定义了部分粗略的用于数据显示的字段。

图片 9

当今在Controller中,我们只是需要给View返回FeatureProductsViewModel对象即可。比如:

public ViewResult Index()
{
    var vm = new FeaturedProductsViewModel();
    return View(vm);
}

今返的是空列表,具体的填充方式在领域模型中,我们随后看领域模型层。

2.1.2 领域逻辑层

新建一个类库,这中含POCOs和一部分虚幻类型。POCOs用来针对领域建模,抽象类型提供抽象作为到世界模型的入口。依赖注入的条件是面向接口而无是具体的好像编程,使得我们可轮换具体实现。

今日我们要呢展现层提供数据。因此用户界面层需要引用领域模型层。对数码访问层的简要抽象可以用Patterns
of Enterprise Application
Architecture一修中说道到的Repository模式。因此定义一个ProductRepository抽象类,注意是抽象类,在圈子模型库中。它定义了一个收获具有特价商品之纸上谈兵方法:

public abstract class ProductRepository
{
    public abstract IEnumerable<Product> GetFeaturedProducts();
}

是主意的Product类中唯有定义了货的骨干信息以名以及单价。整个涉及图如下:

图片 10

现在来拘禁表现层,HomeController中之Index方法应该要下ProductService实例类来取商品列表,执行价格打折,并且把Product类似转化为ProductViewModel实例,并拿该实例加入到FeaturesProductsViewModel中。因为ProductService有一个包含类型也ProductReposity抽象类的构造函数,所以这边可以经构造函数注入实现了ProductReposity抽象类的实例。这里跟事先的卓绝酷区别是,我们从不动new关键字来立即new一个目标,而是经构造函数的措施传入具体的兑现。

现在来拘禁表现层代码:

public partial class HomeController : Controller
{
    private readonly ProductRepository repository;

    public HomeController(ProductRepository repository)
    {
        if (repository == null)
        {
            throw new ArgumentNullException("repository");
        }

        this.repository = repository;
    }

    public ViewResult Index()
    {
        var productService = new ProductService(this.repository);

        var vm = new FeaturedProductsViewModel();

        var products = productService.GetFeaturedProducts(this.User);
        foreach (var product in products)
        {
            var productVM = new ProductViewModel(product);
            vm.Products.Add(productVM);
        }

        return View(vm);
    }

}

于HomeController的构造函数中,传入了落实了ProductRepository抽象类的一个实例,然后将欠实例保存于概念的村办的独自读的ProductRepository类型的repository对象中,这虽是百里挑一的通过构造函数注入。在Index方法被,获取数据的ProductService类中之最主要功用,实际上是透过传播的repository类来代劳完成的。

ProductService类是一个纯的小圈子对象,实现如下:

public class ProductService
{
    private readonly ProductRepository repository;

    public ProductService(ProductRepository repository)
    {
        if (repository == null)
        {
            throw new ArgumentNullException("repository");
        }

        this.repository = repository;
    }

    public IEnumerable<DiscountedProduct> GetFeaturedProducts(IPrincipal user)
    {
        if (user == null)
        {
            throw new ArgumentNullException("user");
        }

        return from p in
                        this.repository.GetFeaturedProducts()
                select p.ApplyDiscountFor(user);
    }
}

得看到ProductService也是经构造函数注入的办法,保存了实现了ProductReposity抽象类的实例,然后凭该实例中之GetFeatureProducts方法,获取原始列表数据,然后进行打折处理,进而实现了投机之GetFeaturedProducts方法。在该GetFeaturedProducts方法被,跟之前不同之地方在,现在底参数是IPrincipal,而休是事先的bool型,因为判断用户的景,这是一个业务逻辑,不该以见层处理。IPrincipal是BCL中的品种,所以未设有额外的负。我们相应根据接口编程IPrincipal是应用程序用户的平等种标准措施。

此间用IPrincipal作为参数传递给某方法,然后再次中调用实现之法门是凭借注入中的章程注入的手腕。和构造函数注入一样,同样是拿里面贯彻代理于了流传的因对象。

而今我们特剩下零星块地方没有处理了:

  • 从来不ProductRepository的切切实实落实,这个很容易实现,后面停放数据访问层里面去处理,我们一味需要创造一个有血有肉的落实了ProductRepository的数据访问类即可。
  • 默认上,ASP.NET MVC
    希望Controller对象来好的默认构造函数,因为我们于HomeController中上加了新的构造函数来注入依赖,所以MVC框架不知底怎样缓解创造实例,因为有依靠。这个题材得以透过出一个IControllerFactory来化解,该目标好创造一个切实可行的ProductRepositry实例,然后传被HomeController这里不多云。

当今我们的小圈子逻辑层已经勾勒好了。在该层,我们唯有操作领域模型对象,以及.NET
BCL
中之骨干对象。模型使用POCOs来代表,命名吧Product。领域模型层必须能和外围进行交流(database),所以待一个华而不实类(Repository)来常成功就同效应,并且在必要之早晚,可以替换具体落实。

2.1.3 数据访问层

当今咱们得行使LINQ to
Entity来贯彻具体的多寡访问层逻辑了。因为要是促成世界模型的ProductRepository抽象类,所以需要引入世界模型层。注意,这里的因变成了数额访问层依赖领域模型层。跟之前的恰好相反,代码实现如下:

public class SqlProductRepository : Domain.ProductRepository
{
    private readonly CommerceObjectContext context;

    public SqlProductRepository(string connString)
    {
        this.context =
            new CommerceObjectContext(connString);
    }

    public override IEnumerable<Domain.Product> GetFeaturedProducts()
    {
        var products = (from p in this.context.Products
                        where p.IsFeatured
                        select p).AsEnumerable();
        return from p in products
                select p.ToDomainProduct();
    }
}

当此处要注意的凡,在天地模型层中,我们定义了一个叫吧Product的天地模型,然后又数据访问层中Entity
Framework帮我们啊死成了一个曰也Product的多寡访问层实体,他是和db中之Product表一一对应的。所以我们在艺术返回的上,需要将品种从db中的Product转换为世界模型中之POCOs
Product对象。

图片 11 

Domain
Model中之Product是一个POCOs类型的靶子,他一味包含领域模型中需要利用的有些着力字段,DataAccess中的Product对象是投到DB中之实体,它包含数据库被Product表定义之所有字段,在数码见层中我们
定义了一个ProductViewModel数据表现的Model。

顿时简单只对象期间的变换很简单:

public class Product
{
    public Domain.Product ToDomainProduct()
    {
        Domain.Product p = new Domain.Product();
        p.Name = this.Name;
        p.UnitPrice = this.UnitPrice;
        return p;
    }
}

2.2 分析

2.2.1 依赖关系图

当今,整个体系的倚重关系图如下:

图片 12

呈现层与数目访问层都靠领域模型层,这样,在前边的case中,如果我们新添加一个UI界面;更换同种数据源的仓储和得方式,只需要修改对应层的代码即可,领域模型层保持了安定。

2.2.2 时序图

不折不扣系统的时序图如下:

图片 13

系统启动的早晚,在Global.asax中创造了一个自定义了Controller工厂类,应用程序将其保存于本土就少种植,当页面请求进入的时,程序出发该工厂类的CreateController方法,并招来web.config中的数据库连接字符串,将该传递给新的SqlProductRepository实例,然后以SqlProductRepository实例注入及HomeControll中,并回。

下一场使用调用HomeController的实例方法Index来创造新的ProductService类,并经构造函数传入SqlProductRepository。ProductService的GetFeaturedProducts
方法代理于SqlProductRepository实例去落实。

说到底,返回填充好了FeaturedProductViewModel的ViewResult对象吃页面,然后MVC进行适度的展现。

2.2.3 新的组织

于1.1之兑现着,采用了三重合架构,在改良后底落实着,在UI层和天地模型层中在了一个表现模型(presentation
model)层。如下图:

图片 14

 

用Controllers和ViewModel从表现层移到了展现模型层,仅仅用视图(.aspx和.ascx文件)和聚合根对象(Composition
Root)保留在了表现层中。之所以如此处理,是足以让尽可能的让表现层能够可部署而另部分尽可能的可维持无转移。

3. 结语

鲁咱尽管编写出了紧耦合的代码,有时候觉得分层了就算可以化解当时同样题目,但是多数底时刻,都并未科学的落实分层。之所以容易写有紧耦合的代码来一个原因是为编程语言或出环境允许我们若用一个初的实例对象,就好使new关键字来实例化一个。如果我们用加上依赖,Visual
Studio有些时候可自行帮咱添加引用。这令我们非常易就犯错,使用new关键字,就可能会见引入以来;添加引用就见面发出依赖。

减少new引入的凭与紧耦合最好的法门是以构造函数注入依赖这种设计模式:即如我们要一个负的实例,通过构造函数注入。在第二单有的兑现演示了争对抽象而非是具体编程。

构造函数注入是反转控制的一个例子,因为我们反转了对借助之决定。不是用new关键字创建一个实例,而是以这种行为委托为了第三着实现。

要本文能够让大家了解什么真正兑现三重合架构,编写松散耦合,可保护,可测试性的代码提供部分助。

 

 

相关文章