免去看重就是松耦合

铲除倚重就是松耦合,松耦合就自然好吧?当然不是程序的耦合度与内聚度要放在一块儿说,当然,对于世界层来说,适当的滑坡类与类之间的看重性是很有必不可少的,看下边我的那段代码

  往日平昔不知道依赖注入有哪些好处,甚至以为它是鸡肋,现在思想,当时真是可笑。

一个事例,一个订单系统,它依照国家有例外的下单方法,对于低层(DATA层)是一致的,但对于世界层,也叫工作逻辑层,它是不一致的,可能小扶桑下单打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

  当所有项目相当庞大,各类艺术之间的调用非常复杂,那么,可以想像一下,倘若说没有此外的分开模块的想法,各样关系非常的扑朔迷离,不便于维护以及查找bug等等。这些时候,就需要一个东西,去将如此多复杂的实物分离开来,很喜欢的一句话:高内聚,低耦合。

  举个栗子,现在一个很简单的功能,可能只需要经过一些办法相互调用就足以实现,OK,那么随着需求的扩展,现在丰盛了多少个功效,那么,大家把相同的事物划分为一个类吧,类经过而生,同样,类似的,一层一层如下所示:

主意–>类–>接口–>模块–>甚至把各样模块之间的涉及抽象出来(比如IOC/DI)

 

 

  总而言之,一个为主的想想就是,对于一个大项目以来,把持有的模块尽可能的多分多少个,一个内部的事物太多以来,多分开几个写,一句话表明:高内聚,低耦合

 

浅谈依赖注入

 

最近几天在看一本名为Dependency Injection in
.NET
 的书,重要讲了怎么是凭借注入,使用信赖注入的独到之处,以及.NET平台上看重注入的各样框架和用法。在这本书的起初,讲述了软件工程中的一个要害的眼光就是关爱分离(Separation
of
concern, SoC)。看重注入不是目的,它是一多元工具和伎俩,最终的目标是协理我们付出出松散耦合(loose
coupled
)、可保障、可测试的代码和顺序。这条原则的做法是豪门领悟的面向接口,或者说是面向抽象编程。

关于咋样是依靠注入,在Stack
Overflow下边有一个题目,什么样向一个5岁的儿童解释依赖注入,其中得分最高的一个答案是:

“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,然后添加一个名为Product瑟维斯(Service)的类:

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(Service)在这一层,同时,还需要倚重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的类,这类别应该是属于世界模型层的。
  • 彰显层中掺入了控制某个用户是否是会员的逻辑。这种业务逻辑应该是
    业务逻辑层中应有处理的,所以也应有放权世界模型层
  • Product瑟维斯(Service)因为依靠了多少访问层,所以也会凭借在web.config
    中配备的数据库连接字符串等音讯。这使得,整个事情逻辑层也急需借助这一个安排才能正常运转。
  • 在View中,包含了太多了函数性功效。他履行了强制类型转换,字符串格式化等操作,这么些效应应该是在界面突显得模型中做到。

下边可能是我们大部分写代码时候的落实,
UI界面层去依赖了数额访问层,有时候偷懒就直接引用了这一层,因为实体定义在里边了。业务逻辑层也是看重数据访问层,直接在业务逻辑之中使用了数码访问层里面的实业。这样使得整个系统紧耦合,并且可测试性差。这现在我们看看,怎样修改这样一个序列,使之达到松散耦合,从而增强可测试性呢?

2 较好的兑现

依傍注入可以较好的化解地点出现的问题,现在可以动用这一思考来重新实现前边的体系。之所以再度实现是因为,前边的实现在一先河的如同就一贯不考虑到增加性和松耦合,使用重构的主意很难达到卓越的效用。对于小的体系来说恐怕仍是可以够,但是对于一个大型的系统,应该是相比较劳碌的。

在写代码的时候,要保管好凭借,在前头的实现这种,代码直接决定了倚重:当ProductService(Service)需要一个ObjectContext类的如同,直接new了一个,当HomeController需要一个Product瑟维斯的时候,直接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中。因为Product瑟维斯(Service)有一个饱含类型为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方法中,获取数据的Product瑟维斯(Service)类中的重要意义,实际上是因而传播的repository类来代理完成的。

Product瑟维斯(Service)类是一个纯粹的世界对象,实现如下:

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(Service)也是经过构造函数注入的法子,保存了落实了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关键字创制一个实例,而是将那种作为委托给了第三方实现。

企望本文可以给我们探听咋样确实落实三层架构,编写松散耦合,可珍惜,可测试性的代码提供部分声援。

 

http://stackoverflow.com/questions/1362962/when-not-to-use-ioc-and-di

参考http://www.cnblogs.com/yangecnu/p/Introduce-Dependency-Injection.html

相关文章