违反 LSP 的异常处理装饰器

Exception Handling Decorator violating LSP

我目前正在阅读 Mark Seeman 和 Steven van Deursen 的书 "Dependency Injection"(第二版,但相同的例子在第一版中)。

在第 9.2.2 章 "Reporting exceptions using the Decorator pattern" 他提出了一个异常处理装饰器,它通过捕获某些异常并打开一个警告框来装饰抽象,而不是将异常冒泡给消费者。

他表示遵守 SRP 和 OCP,我可以遵循。

然而,在我看来,这违反了 Liskov substitution principle

抽象抛出的异常属于合约。如果它们在装饰器中被捕获并转换为警告消息,消费者不会明确知道这一点。消费者要么隐含地知道装饰器被用来处理异常,在我看来这很奇怪,要么他不知道,在这种情况下他必须自己关心异常处理以遵守合同和 LSP,这会使装饰器变得无用。

在我看来,消费者一定不知道抽象被装饰了,他应该遵守抽象提供的合同。

我的提议是一个新接口 IExceptionHandlingAbstraction,它由一个适配器实现,该适配器捕获 IAbstraction 的异常并将其转换为警告框。这样消费者就可以依赖 IExceptionHandlingAbstraction 的契约,并明确知道他不需要自己处理异常。

因为我非常信任 Mark 的帖子、答案和书籍,所以我不太确定我是否遗漏了什么。

the consumer must not know that an abstraction was decorated, he should adhere to the contract the abstraction provides.

没错。为了遵守 LSP,抽象的两个实现都应该遵守同一个契约。但现在的问题是:合约到底是什么?消费者能期待什么。

与 Java 相比,.NET 不强制将异常作为方法定义的一部分。我认为这很好。您不能也不应该将抛出的异常限制为抽象。当您抛出客户端期望处理的异常时,这当然会改变。在那种情况下,异常类型应该是合同的一部分。

在那个特定的装饰器示例中,IMO 的一个更大的问题是异常不会冒泡。它被完全吞噬了。这当然可以被视为违反(隐式)契约,因为开发人员会期望在没有抛出异常时调用成功。所以我同意这个例子有点幼稚并且违反了 LSP。

但是,该示例并不是 full-proof、100% SOLID 解决方案,而是作为一个示例来展示使用装饰器进行拦截的强大功能。还要注意,在第 10 章中,我们解释了 100% SOLID 既不可能也不可取。这都是关于权衡的,也许这个装饰器示例在您正在构建的特定客户端应用程序中运行良好,尽管我确实希望消费者(表单)需要知道操作是否成功,因为您通常希望在完成时关闭表单.但那样的话,我希望第10章中规定的设计是更好的解决方案。

My proposal would be a new interface IExceptionHandlingAbstraction that is implemented by an adapter that catches the exceptions of IAbstraction and transforms it to alert boxes

创建一个新的抽象允许消费者得到通知或通知失败是解决 LSP 违规的一个很好的解决方案。