如何理解松散耦合应用程序中的大局?

我们一直在使用松耦合和依赖注入开发代码。

很多“服务”风格的类都有一个构造函数和一个实现接口的方法。 每个班级都很容易孤立地理解。

然而,由于耦合的松弛,查看课程不会告诉你关于它的类或它适合放大图的地方。

使用Eclipse跳转到协作者并不容易,因为您必须通过界面进行操作。 如果接口Runnable ,那就是在寻找哪一类是在实际插入没有帮助。真的有必要回到DI容器定义,并试图从那里理出头绪。

以下是来自依赖注入服务类的一行代码: -

  // myExpiryCutoffDateService was injected, 
  Date cutoff = myExpiryCutoffDateService.get();

这里的耦合尽可能松散。 有效期限以任何方式实际执行。

以下是它在更多耦合应用程序中的样子。

  ExpiryDateService = new ExpiryDateService();
  Date cutoff = getCutoffDate( databaseConnection, paymentInstrument );

从紧密耦合的版本中,我可以推断,截止日期在某种程度上由使用数据库连接的支付工具确定。

我发现第一种风格的代码比第二种风格的代码难以理解。

你可能会争辩说,在阅读这门课时,我不需要知道截止日期是如何计算出来的。 确实如此,但是如果我正在缩小bug或确定增强功能需要插入的位置,那么这是非常有用的信息。

有没有人遇到过这个问题? 你有什么解决方案? 这只是一些适应? 是否有任何工具可以让班级连线的方式可视化? 我应该让课程更大还是更融合?

(由于我对任何答案感兴趣,故意将这个问题留在容器不可知的位置)。


尽管我不知道如何在单个段落中回答这个问题,但我试图在博客文章中回答它:http://blog.ploeh.dk/2012/02/02/LooseCouplingAndTheBigPicture.aspx

总而言之,我发现最重要的一点是:

  • 理解松散耦合的代码库需要不同的思维模式 。 虽然“跳入合作者”很难,但它也应该或多或少不相关。
  • 松耦合是关于理解一部分而不理解整体的 。 你应该很少需要同时理解这一切。
  • 在纠正错误时,应该依靠堆栈跟踪而不是代码的静态结构来了解协作者。
  • 编写代码的开发人员有责任确保它易于理解 - 这不是开发人员阅读代码的责任。

  • 一些工具知道DI框架并知道如何解决依赖关系,使您能够以自然的方式导航代码。 但是,如果无法使用,则只需使用IDE提供的所有功能即可。

    我使用Visual Studio和一个定制的框架,所以你描述的问题是我的生活。 在Visual Studio中,SHIFT + F12是我的朋友。 它显示对光标下的符号的所有引用。 过了一段时间,你习惯于通过代码进行必要的非线性导航,并且根据“哪个类实现此接口”和“注入/配置站点在哪里”这一术语来考虑是第二性质,以便我可以看到哪个类正被用来满足这种接口依赖性“。

    还有一些可用于VS的扩展,它们提供UI增强功能以​​帮助实现此目的,例如Productivity Power Tools。 例如,您可以将鼠标悬停在界面上,会弹出一个信息框,您可以单击“实施者”查看解决方案中实现该界面的所有类。 您可以双击跳转到任何这些类的定义。 (无论如何,我仍然通常只使用SHIFT + F12)。


    我刚刚就此进行了内部讨论,最后写了这篇文章,我觉得这篇文章太好了,不愿意分享。 我几乎是在未经编辑的情况下复制它,但即使它是更大的内部讨论的一部分,我认为它大部分可以单独使用。

    讨论的内容是介绍一个名为IPurchaseReceiptService的自定义接口,以及是否应该使用IObserver<T>替换它。


    那么,我不能说我有关于这些的强大数据点 - 这只是我正在追求的一些理论......然而,我目前关于认知开销的理论是这样的:考虑你的特殊IPurchaseReceiptService

    public interface IPurchaseReceiptService
    {
        void SendReceipt(string transactionId, string userGuid);
    }
    

    如果我们将它保留为它当前的标题接口,则它只有一个SendReceipt方法。 这很酷。

    没有那么酷的是,你必须为接口提供一个名称,并为该方法提供另一个名称。 两者之间有一些重叠:单词Receipt出现两次。 IME,有时候这种重叠可能会更加明显。

    此外,接口的名称是IPurchaseReceiptService ,这也不是特别有用。 服务后缀实质上是新的管理器,而IMO是一种设计气味。

    此外,您不仅需要命名该接口和方法,还必须在使用该变量时命名该变量:

    public EvoNotifyController(
        ICreditCardService creditCardService,
        IPurchaseReceiptService purchaseReceiptService,
        EvoCipher cipher
    )
    

    在这一点上,你基本上说过三次同样的事情。 根据我的理论,这是认知开销,以及设计可以并且应该更简单的气味。

    现在,将其与使用像IObserver<T>这样的着名界面进行对比:

    public EvoNotifyController(
        ICreditCardService creditCardService,
        IObserver<TransactionInfo> purchaseReceiptService,
        EvoCipher cipher
    )
    

    这可以让你摆脱官僚主义,减少设计的核心问题。 您仍然有意向 - 揭示命名 - 您只将设计从“类型名称角色提示”转换为“参数名称角色提示”。


    当谈到关于“不连贯性”的讨论时,我毫不IObserver<T>使用IObserver<T>会神奇地使这个问题消失,但我对此有另一种理论。

    我的理论是,许多程序员发现接口编程如此困难的原因正是因为他们习惯于Visual Studio的定义功能(顺便说一句,这是工具如何腐蚀头脑的又一例子)。 这些程序员永远处于一种心境,他们需要知道'界面的另一面'是什么。 为什么是这样? 难道是因为抽象很差?

    这与RAP相关联,因为如果您确认程序员相信每个接口背后都有一个特定的实现,那么他们就认为接口只能这样,这就难怪了。

    但是,如果您应用RAP,我希望程序员能够慢慢学会,在特定接口背后,可能会有该接口的任何实现,并且它们的客户端代码必须能够处理该接口的任何实现,而不会改变其正确性系统。 如果这个理论成立,我们刚刚将Liskov替代原则引入到代码库中,而不会吓倒任何他们不明白的高眉概念的人:)

    链接地址: http://www.djcxy.com/p/82273.html

    上一篇: How to understand the big picture in a loose coupled application?

    下一篇: How to inject dependencies into the global.asax.cs