【问题标题】:StructureMap and nTier ApplicationStructureMap 和 nTier 应用程序
【发布时间】:2015-02-14 16:31:26
【问题描述】:

想知道是否有人可以向我提供一些指导。我的标准应用程序设置一直是 nTier 应用程序(Presentation、Business、Data,通常是 Common)。我尽量避免设置和 IoC 容器(在其他人的应用程序中使用它们很长时间,只是没有设置它们),但最终不得不冒险。

我对 IoC 的理解允许依赖注入,这反过来使单元测试成为可能(好得多)所以在我的脑海中,我想至少在业务层上执行单元测试......但是每个设置示例up IoC 之类的 StructureMap 使 IoC 在表示层上。所以...我要问的是带有 IoC 的 nTier 应用程序的“最佳实践”是什么。

谢谢。

【问题讨论】:

    标签: c# asp.net-mvc inversion-of-control structuremap n-tier-architecture


    【解决方案1】:

    DI 的主要好处不是单元测试(尽管这肯定是一个好处)。主要好处是松耦合。 “可测试”的应用程序不一定是松耦合的。

    但是,松耦合带来的不仅仅是可测试性。

    松散耦合的好处

    1. 后期绑定(服务可以与其他服务交换,通常无需更改现有代码
    2. 可扩展性(代码可以扩展,通常无需更改现有代码
    3. 并行开发(定义了多个开发人员可以遵守的抽象合同)
    4. 可维护性(职责明确的类更易于维护)
    5. 可测试性(类更容易测试)。

    恕我直言,当将 DI 与软件模式相结合时,可扩展性绝对是主要优势。考虑以下类型:

    public interface IWriter
    {
        void WriteSomething();
    }
    
    public interface ISomeService
    {
        void Write();
    }
    

    您可以使用Decorator Pattern 扩展服务:

    public class NullWriter : IWriter
    {
        public void WriteSomething()
        {
            // Do nothing - this is a "null object pattern".
        }
    }
    
    public class HelloWriter : IWriter
    {
        public readonly IWriter innerWriter;
    
        public HelloWriter(IWriter innerWriter)
        {
            if (innerWriter == null)
                throw new ArgumentNullException("innerWriter");
            this.innerWriter = innerWriter;
        }
    
        public void WriteSomething()
        {
            this.innerWriter.WriteSomething();
    
            Console.WriteLine("Hello.");
        }
    }
    
    public class GoodbyeWriter : IWriter
    {
        public readonly IWriter innerWriter;
    
        public GoodbyeWriter(IWriter innerWriter)
        {
            if (innerWriter == null)
                throw new ArgumentNullException("innerWriter");
            this.innerWriter = innerWriter;
        }
    
        public void WriteSomething()
        {
            this.innerWriter.WriteSomething();
    
            Console.WriteLine("Goodbye.");
        }
    }
    
    public class SomeService : ISomeService
    {
        private readonly IWriter writer;
    
        public SomeService(IWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException("writer");
        }
    
        public void Write()
        {
            this.writer.WriteSomething();
        }
    }
    

    上面的内容会像这样连接起来:

    // Composition Root
    var nullWriter = new NullWriter();
    var goodbyeWriter = new GoodbyeWriter(nullWriter);
    var helloWriter = new HelloWriter(goodbyeWriter);
    var service = new SomeService(helloWriter);
    // End Composition Root
    
    // Execute
    service.Write();
    
    //Writes:
    
    //Hello.
    //Goodbye.
    

    现在,已经设置了场景,您可以扩展 SomeService 所做的事情而无需更改任何现有类型。应用程序中唯一需要更改的部分是composition root

    public class HowAreYouWriter : IWriter
    {
        public readonly IWriter innerWriter;
    
        public HowAreYouWriter(IWriter innerWriter)
        {
            if (innerWriter == null)
                throw new ArgumentNullException("innerWriter");
            this.innerWriter = innerWriter;
        }
    
        public void WriteSomething()
        {
            this.innerWriter.WriteSomething();
    
            Console.WriteLine("How are you?");
        }
    }
    
    // Composition Root
    var nullWriter = new NullWriter();
    var goodbyeWriter = new GoodbyeWriter(nullWriter);
    var howAreYouWriter = new HowAreYouWriter(goodbyeWriter);
    var helloWriter = new HelloWriter(howAreYouWriter);
    var service = new SomeService(helloWriter);
    // End Composition Root
    
    // Execute
    service.Write();
    
    //Writes:
    
    //Hello.
    //How are you?
    //Goodbye.
    

    约定优于配置

    DI 的另一个(经常被忽视的)好处是Convention over Configuration。当将构造函数注入与 DI 容器结合使用时,它们中的许多都提供了将 ISomeService 自动映射到 SomeService 的能力。一些容器(如 StructureMap)还可以构建您自己的约定。

    好处并不明显,因为直到您使用约定注册数十种类型时,它才真正开始得到回报。但是,如果使用它们,您可以大大减少编写应用程序所需的代码量。

    N 层应用

    对于单个应用程序,通常有一个composition root 尽可能靠近应用程序的入口点。在 MVC 中,这将在 HttpApplication.Start 方法中。

    但是,这可能会有所不同,具体取决于您是否将应用程序设计的层视为DI Friendly LibrariesDI Friendly Frameworks,以及您是否将应用程序的一部分视为您添加到它之后的“插件”已构建(基本上是制作可以加载动态依赖项的组合根)。

    基本上有 3 种常用的方法来解决这个问题:

    1. 公开所有类型并将它们组合在包含您的表示层的同一个项目中。
    2. 在每一层都放一个组合根,让每一层的公共API在内部使用DI。然后使用主项目编写该层的公共 API。通常,您还需要在从主Compose 方法调用的每一层上创建一个公共Compose 方法。
    3. 制作一个单独的合成根项目以将所有部分合成在一起。

    我什至看到有些人建议将所有“层”放入一个项目中,如果您不打算单独使用这些部分,那么没有太大的缺点。 NuGet Gallery 就是以这种方式构建的此类项目之一。

    恕我直言,公开所有内容并将组合根放在主应用程序中通常是单个多层应用程序的最佳选择,该应用程序的部分不打算与另一个应用程序一起使用。

    最后一句话

    如果您对学习 DI 很认真,请阅读 Mark Seemann 的《Dependency Injection in .NET》一书。您可能不认为 DI 是一个足够大的开发领域,可以单独研究,但本书确实提供了许多超越 DI 的好处,例如 SOLID 原则。

    【讨论】:

    • ...但是 nTier 应用程序的布局是什么?你把 IoC 放在哪一层? IoC 不需要范围来查看接口和具体类吗?如果是这样,这是否意味着您需要将 IoC 放在一个单独的项目中,这样它就完全解耦了......但是当您尝试从 App.Start 中调用 Bootstrapper 时,您不会有循环引用表示层(如果您尝试在控制器上使用 DI)。这就是我寻求指导的原因。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-29
    • 1970-01-01
    • 2010-10-29
    • 2010-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多