楼梯模式实现

31
我在《通过C#进行自适应编程》一书中发现了“Stairway”模式的描述,但我并不真正理解这个模式应该如何实现:

Stairway pattern (来源)

我有一个客户端程序集:

using ServiceInterface;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            // Have to create service implementation somehow
            // Where does ServiceFactory belong?
            ServiceFactory serviceFactory = new ServiceFactory();
            IService service = serviceFactory.CreateService();
            service.Do();
        }
    }
}

服务接口组件:

namespace Service
{
    public interface IService
    {
        void Do();
    }
}

服务实现汇编:

using ServiceInterface;

namespace ServiceImplementation
{
    public class PrintService : IService
    {
        public void Do()
        {
            Console.WriteLine("Some work done");
        }
    }
}
问题是:如何在命名空间中获取对象?在哪里放置实际的对象创建?这不能是的一部分,因为接口程序集不依赖于。但它也不能是或的一部分,因为只应该依赖于。
我想到的唯一解决方案是有一个程序集,它位于这三个(、和)之上,并将注入。我是否漏掉了什么?

4
你是否在考虑使用依赖注入? - Andy Danger Gagne
1
我同意@AndyDangerGagne的观点,有一些DI容器可以为您完成此操作。 - adricadar
1
@AndyDangerGagne,您是在说forth汇编引用所有其他将创建具体服务实现并传递到客户端(现在不是真正的客户端)ctor,其期望任何iservice吗?我可以看到这一点,但这有点破坏了这个美丽的楼梯图:) - d453
3个回答

28

按照Mark Seemann关于依赖注入的卓越书籍,应用程序入口点应该是组合根。在这里,问题更多地涉及依赖反转,即客户端和实现都应该依赖于抽象。

那个图表没有显示的是,但希望本书的其他部分能够清楚地说明的是,入口点自然而然地会引用构造您解析根(控制器、服务等)所需的所有内容。但这是唯一具有此类知识的地方。

请记住,有时客户端拥有它们所依赖的接口是可以接受的:例如,接口ISecurityService可能存在于Controllers程序集中,IUserRepository可能存在于ServiceImplementations程序集中,等等。当然,当超过1个客户端需要访问接口时,这是不可行的。

如果您遵循SOLID原则,您自然会发现依赖注入是必要的,但控制反转容器并不是优先考虑的。我发现自己越来越经常使用纯粹的依赖注入(手动构建解析根)。


2
我认为这应该包含在书中,因为我在阅读后不久就不得不进行研究。 - reggaeguitar
2
@TedOnTheNet 谢谢!我会在第二版中进行澄清 ;) - Gary McLean Hall
1
如果我正确理解你的问题,那么答案是否定的。核心API应该引用接口而不是实现。由组合根(几乎总是包含应用程序入口点的项目)通过某种形式的依赖注入向客户端提供实现。我目前正在写第二版,并将在新的一年发布。 - Gary McLean Hall
@GaryMcLeanHall 我发现这个答案与其他评论者的原因类似;我认为我也需要理解组合根,但在尝试自己应用时,仍然更喜欢通过网络搜索来获取实现细节,而不是返回书籍。另外,据我回忆,您写道这种方法并不一定会导致库/模块的大量增加。我并不信服(!),我认为如果能有更多深入的例子和讨论,可能会更有帮助。 - Nij
@GaryMcLeanHall,你读过Mark Seemann的书吗?你推荐它吗?谢谢。 - zzfima
显示剩余4条评论

2
在这种情况下,Client项目应该包含对ServiceServiceImplementation的引用。这些引用仅用于创建将被DI使用的IoC容器。在应用程序启动时,您需要在IoC容器中注册所有接口实现。
如果您针对Service接口实现ServiceImplementation并基于Service接口编写Client,则不会依赖于ServiceImplementation
您还可以查看“Adaptive Code via C#”样本中如何实现Stairway模式: https://github.com/garymcleanhall/AdaptiveCode/tree/master/Sprints/sample-sprint2-markdown

是的,我明白了。但是书中的UML图清楚地表明“控制器”没有对“服务实现”的引用。这就是我最初感到困惑的原因。 - d453
1
没错。因此,“Client”引用了所有程序集,但它们之间没有依赖关系。 - Krystian Kulig

1
我会把它放在ServiceFactory中。您需要一些参数,例如在工厂构造函数中传递或从配置中检索等,以确定由工厂创建哪个IService实现。

你会把 ServiceFactory 放在哪里呢?如果我们将其放置在 ServiceImplementation 中,那么客户端代码将引用接口和实现程序集。如果我们将其放置在接口程序集中,情况会变得非常混乱,因为接口和实现程序集会相互引用(如果我没记错的话是不可能的)。 - d453
1
我所暗示的是依赖注入类型的解决方案,就像@AndyDangerGagne所说的那样。 工厂需要知道如何实例化实现IService接口的类。 如果您不想在客户端程序集中引用接口和实现程序集,我唯一看到的解决方案是有另一个程序集来处理。 您已经在问题中提到了这一点。 - Hintham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接