C# 体系结构:依赖项注入、松散耦合的程序集、实现隐藏

C# 体系结构:依赖项注入、松散耦合的程序集、实现隐藏,c#,.net,design-patterns,dependency-injection,architecture,C#,.net,Design Patterns,Dependency Injection,Architecture,我一直在做一个个人项目,除了为自己做一些有用的东西之外,我还试图用它来继续寻找和学习建筑课程。一个这样的教训就像一个骑在自行车小径中间的科迪亚克熊,我一直在挣扎着。 这个问题本质上是依赖注入、程序集解耦和实现隐藏(即,使用内部类实现我的公共接口)交叉点问题的混合体 在我的工作中,我通常会发现应用程序的各个层都有自己的接口,这些接口是公开的,但在内部实现的。每个程序集的DI代码将内部类注册到公共接口。此技术可防止外部程序集更新实现类的实例。然而,我在构建这个解决方案时读到的一些书对此提出了异议。与

我一直在做一个个人项目,除了为自己做一些有用的东西之外,我还试图用它来继续寻找和学习建筑课程。一个这样的教训就像一个骑在自行车小径中间的科迪亚克熊,我一直在挣扎着。 这个问题本质上是依赖注入、程序集解耦和实现隐藏(即,使用内部类实现我的公共接口)交叉点问题的混合体

在我的工作中,我通常会发现应用程序的各个层都有自己的接口,这些接口是公开的,但在内部实现的。每个程序集的DI代码将内部类注册到公共接口。此技术可防止外部程序集更新实现类的实例。然而,我在构建这个解决方案时读到的一些书对此提出了异议。与我之前的想法相冲突的主要事情与DI组合根有关,并且应该在哪里保留给定实现的接口。如果我将依赖项注册移动到单个全局合成根(正如Mark Seemann所建议的),那么我就可以摆脱每个程序集必须运行其自己的依赖项注册。但是,缺点是实现类必须是公共的(允许任何程序集实例化它们)。至于解耦程序集,Martin Fowler指示在项目中使用使用接口的代码(而不是实现接口的代码)放置接口。作为一个例子,这是他提供的一个图表,作为对比,这是一个关于我通常如何实现相同解决方案的图表(好的,这些不完全相同;请关注箭头,并注意实现箭头何时跨越程序集边界而不是组合箭头)

马丁风格

我通常看到的

我立即在Martin的图中看到了优势,即它允许将较低的程序集替换为另一个程序集,因为它有一个类在其上面的层中实现接口。然而,我也看到了这个看似主要的缺点:如果您想从上层交换出程序集,那么您实际上“窃取”了底层正在实现的接口

在考虑了一点之后,我决定在两个方向上完全解耦的最好方法是让接口在它们自己的程序集中指定层之间的契约。考虑这个更新的图表:

这是疯子吗?它开对了吗?在我看来,这似乎解决了接口隔离的问题。但是,它并不能解决无法将实现类隐藏为内部类的问题。有什么合理的办法可以做吗?我不应该为此担心吗

我脑子里想的一个解决方案是让每一层实现代理层的接口两次;一次使用公共类,一次使用内部类。这样,public类只能包装/装饰内部类,如下所示:

namespace MechanismProxy // Simulates Mechanism Proxy Assembly
{
    public interface IMechanism
    {
        void DoStuff();
    }
}

namespace MechanismImpl // Simulates Mechanism Assembly
{
    using MechanismProxy;

    // This class would be registered to IMechanism in the DI container
    public class Mechanism : IMechanism
    {
        private readonly IMechanism _internalMechanism = new InternalMechanism();

        public void DoStuff()
        {
            _internalMechanism.DoStuff();
        }
    }

    internal class InternalMechanism : IMechanism
    {
        public void DoStuff()
        {
            // Do whatever
        }
    }
}

某些代码可能如下所示:

namespace MechanismProxy // Simulates Mechanism Proxy Assembly
{
    public interface IMechanism
    {
        void DoStuff();
    }
}

namespace MechanismImpl // Simulates Mechanism Assembly
{
    using MechanismProxy;

    // This class would be registered to IMechanism in the DI container
    public class Mechanism : IMechanism
    {
        private readonly IMechanism _internalMechanism = new InternalMechanism();

        public void DoStuff()
        {
            _internalMechanism.DoStuff();
        }
    }

    internal class InternalMechanism : IMechanism
    {
        public void DoStuff()
        {
            // Do whatever
        }
    }
}

。。。当然,我仍然需要解决一些关于构造函数注入和将注入公共类的依赖项传递给内部类的问题。还有一个问题是,外部程序集可能会更新公共机制。。。我需要一种方法来确保只有DI容器可以做到这一点。。。我想如果我能弄明白,我甚至不需要内部版本。无论如何,如果有人能帮助我理解如何克服这些体系结构问题,我将不胜感激。

这是一个基于观点的话题,但既然你问了,我会给出我的

您对创建尽可能多的组件以使其尽可能灵活的关注是非常理论化的,您必须权衡实际价值与成本。 不要忘记,程序集只是编译代码的容器。只有当您查看开发、构建和部署/交付它们的流程时,它们才变得最为相关。因此,在做出关于如何准确地将代码拆分为程序集的好决定之前,您必须提出更多的问题

下面是我事先要问的几个问题的例子:

从您的应用程序域中拆分 以这种方式组装(例如,您真的需要交换吗 集会)

你会有独立的团队来开发这些吗

规模方面的范围是什么(LOC和团队规模)

是否需要保护实现不可用/不可见?例如,这些是外部接口还是内部接口

您真的需要依赖程序集作为一种机制来强制您的体系结构分离吗?或者是否有其他更好的措施(例如代码审查、代码检查等)

您的调用是否真的只发生在程序集之间,还是在某个时候需要远程调用

您必须使用私有程序集吗

密封类是否有助于强制实施您的体系结构

对于一个非常笼统的观点,不考虑这些额外的因素,我会站在Martin Fowler的图表一边,因为这只是如何提供/使用接口的标准方式。如果您对这些问题的回答通过进一步拆分/保护代码显示出附加价值,那么也可以。但是您必须告诉我们更多关于您的应用程序域的信息,并且您必须能够很好地证明这一点

因此,在某种程度上,你面临着两个古老的智慧:

  • 架构倾向于遵循组织设置
  • 很容易过度设计(过于复杂)架构,但这是非常困难的