【问题标题】:facade design pattern立面设计模式
【发布时间】:2012-11-16 10:40:44
【问题描述】:

最近我一直在尝试遵循 TDD 方法,这导致了很多子类,以便可以轻松地模拟依赖等。

例如,我可以说RecurringProfile,它又具有可以应用于它的方法/操作,例如MarkAsCancel、RenewProfile、MarkAsExpired 等。由于 TDD,这些都已实现作为“小”类,如 MarkAsCancelService 等。

为可以在RecurringProfile 上执行的各种方法/操作创建“外观”(单例)是否有意义,例如具有类RecurringProfileFacade。这将包含方法,这些方法将代码委托给实际的子类,例如:

public class RecurringProfileFacade
{
    public void MarkAsCancelled(IRecurringProfile profile)
    {
        MarkAsCancelledService service = new MarkAsCancelledService();
        service.MarkAsCancelled(profile);
    }
    public void RenewProfile(IRecurringProfile profile)
    {
        RenewProfileService service = new RenewProfileService();
        service.Renew(profile);
    }
    ...
}

请注意,上面的代码不是实际代码,实际代码将使用构造函数注入的依赖项。这背后的想法是,此类代码的使用者不需要知道他们需要调用哪些类/子类的内部细节,而只需访问相应的“外观”。

首先,这是“外观”模式,还是其他形式的设计模式?

如果上述内容有意义,那么接下来的另一个问题是——考虑到它们没有任何特定的业务逻辑功能,你会对这些方法进行单元测试吗?

【问题讨论】:

  • 可能第二种方法应该是 RenewProfile,而不是 MarkAsCancelled?
  • 是的,我的错误 - 将编辑。

标签: c# unit-testing design-patterns tdd facade


【解决方案1】:

如果您打算将代码作为库公开给其他人,我只会创建这样的外观。您可以创建一个外观,它是其他人使用的界面。

这将为您提供一些稍后更改实现的能力。

如果不是这样,那么这个门面有什么目的呢?如果一段代码想要调用外观上的一个方法,它将依赖于整个外观。最好保持较小的依赖关系,因此调用代码会更好地依赖于 MarkAsCancelledService,而不是依赖于 RecurringProfileFacade。

【讨论】:

    【解决方案2】:

    在我看来,这是一种外观模式,因为您将服务抽象在简单的方法之后,尽管外观模式通常在我认为它们的方法背后有更多的逻辑。原因是外观模式的目的是在更大的代码体上提供简化的接口。

    至于您的第二个问题,我总是对所有内容进行单元测试。但是,在您的情况下,这取决于您取消或更新配置文件时它是否会改变您的项目状态?因为您可以断言状态确实按照您的预期发生了变化。

    【讨论】:

      【解决方案3】:

      如果你的设计“告诉”你可以使用Singleton 为你做一些工作,那么它可能是糟糕的设计。 TDD 应该使您远离考虑使用单例。

      可以在on wikipedia 找到为什么这是一个坏主意(或者可以是一个好主意)的原因

      我对你的问题的回答是:看看其他模式!例如UnitOfWork 和Strategy、Mediator 并尝试使用这些模式实现相同的功能,您将能够比较每种模式的好处。你最终可能会得到一个UnitOfStrategicMediationFacade 或其他东西;-)

      考虑在Code Review 上发布此问题以进行更深入的分析。

      【讨论】:

      • 单例的意思是使用具有单例生命周期的 DI 容器,而不是实际实现单例。还是等价的?
      • 不等价。实现Singleton 意味着采用特定的设计模式。 like this。具有单例生命周期的对象,如 DI-Container 或具有 Container-Lifetime 的容器内的对象也是单例,但没有实际单例实现的缺点。这个答案here 描述它非常好恕我直言。
      【解决方案4】:

      当遇到这类问题时,我通常会尝试从 YAGNI/KISS 的角度进行推理:

      • 您首先需要MarkAsCancelService、MarkAsExpiredService 等吗?这些操作在RecurringProfile 本身不会有更好的归宿吗?

      您说这些服务是您的 TDD 流程的副产品,但 TDD 1. 并不意味着剥离业务实体的所有逻辑和 2. 如果您这样做 外部化一些逻辑,它不必进入服务。如果您无法为提取的类提供比 [BehaviorName]Service 更好的名称,这通常表明您应该停止并重新考虑是否应该真正提取行为。

      简而言之,您的对象应该保持内聚性,这意味着它们不应该封装太多职责,但也不会变得乏力。

      • 假设这些服务是合理的,你真的需要他们的门面吗?如果它只是开发人员的便捷捷径,是否值得额外维护(其中一项服务的更改将导致外观发生更改)?如果其中一项服务的每个消费者都知道如何直接利用该服务,不是更简单吗?

      如果上述内容有意义,那么接下来的另一个问题是 - 考虑到它们没有,你会对这些方法进行单元测试吗? 有什么特殊的业务逻辑功能吗?

      单元测试样板代码确实很痛苦。有些人会承受这种痛苦,而另一些人则认为这不值得。由于此类代码的重复性和可预测性,一个好的折衷方案是自动生成测试,甚至更好的是,自动生成所有样板代码。

      【讨论】:

      • 将它们分成单独的子类的原因是,RenewPayment 方法可以进行单元测试,这样您就可以验证是否创建了新付款,并验证调用到SendNotification 完成,没有实际使用实际代码,而是传入一个模拟NotificationSender。如果他们都在同一个班级,例如RecurringProfile,我认为这是不可能的?
      • 为什么不呢?可以测试模拟 NotificationSender 以查看它是否被使用。当您在对象中有一系列私有方法调用时,通常只验证最终结果和与外部的协作,因为很难检查链内发生了什么。无论如何,我感觉你是在围绕你的主对象编写一整套对象来操纵它的状态,剥夺它真正属于它的所有行为,但也许我缺乏判断的上下文。
      猜你喜欢
      • 2012-11-08
      • 2011-07-11
      • 1970-01-01
      • 2023-03-04
      • 1970-01-01
      • 2012-09-03
      • 1970-01-01
      • 2011-09-28
      相关资源
      最近更新 更多