抽象方法和开闭原则
Posted
技术标签:
【中文标题】抽象方法和开闭原则【英文标题】:Abstract methods and the Open-Closed principle 【发布时间】:2010-10-07 16:42:06 【问题描述】:假设我有以下人为的代码:
abstract class Root
public abstract void PrintHierarchy();
class Level1 : Root
override public void PrintHierarchy()
Console.WriteLine("Level1 is a child of Root");
class Level2 : Level1
override public void PrintHierarchy()
Console.WriteLine("Level2 is a child of Level1");
base.PrintHierarchy();
如果我只查看Level2
类,我可以立即看到Level2.PrintHierarchy
跟在the open/closed principle 之后,因为它自己做了一些事情,并调用了它所覆盖的基本方法。
但是,如果我只看Level1
类,它似乎违反了OCP,因为它没有调用base.PrintHierarchy
——实际上,在C#中,编译器禁止它并显示错误“无法调用一个抽象基成员”。
使Level1
看起来遵循OCP 的唯一方法是将Root.PrintHierarchy
更改为空的虚拟方法,但是我不能再依赖编译器强制派生类来实现PrintHierarchy
。
我在这里维护代码时遇到的真正问题是看到几十个override
方法不调用base.Whatever()
。如果base.Whatever
是抽象的,那么很好,但如果不是,那么Whatever
方法可能是被拉入接口而不是具体的可覆盖方法的候选方法——或者需要重构类或方法其他一些时尚,但无论哪种方式,它都清楚地表明设计不佳。
没有记住 Root.PrintHierarchy
是抽象的或在 Level1.PrintHierarchy
中添加评论,我是否有任何其他选择可以快速确定像 Level1
这样形成的类是否违反 OCP?
在 cmets 中有很多很好的讨论,也有一些很好的答案。我很难弄清楚到底要在这里问什么。我认为让我感到沮丧的是,as @Jon Hanna points out,有时一个虚拟方法只是表示“你必须实现我”,而其他时候它意味着“你必须扩展我——如果你没有调用基本版本,你就会破坏我的设计!”但是 C# 没有提供任何方法来指示您的意思,除了抽象或接口显然是“必须实现”的情况。 (除非代码合同中有一些东西,我认为这有点超出了范围)。
但是如果一种语言确实有一个必须实现与必须扩展的装饰器,如果它不能被禁用,它可能会给单元测试带来巨大的问题。有没有类似的语言?这听起来很像design-by-contract,所以如果它在埃菲尔,我不会感到惊讶。
最终结果可能是as @Jordão says,它完全是上下文相关的;但在我接受任何答案之前,我将让讨论开放一段时间。
【问题讨论】:
您在寻找静态分析工具吗?如果是,那么也许这可能会有所帮助***.com/q/1627024/119477 我认为重点(和问题)是您无法判断仅通过查看Level1
的Level1
是否违反了OCP实现:你不知道它的base
是抽象的。 (因此“如果base.Whatever
是抽象的,那么很好。”)
@Jeff,您无法判断通过查看是否调用了 base
,Level2 也没有违反 OCP。我们可能会在适当时使用base
来帮助坚持它,但如果它不适合方法的目的,那么这样做可能是错误的。
@Mark,我想知道对 OCP 的关注是否分散了我认为是您的核心问题的注意力,我将其总结为“虚拟方法(与抽象方法相对)是否有任何在好的设计中扮演什么角色?”
【参考方案1】:
Root
定义如下: 根对象有一个 PrintHierarchy 方法。它只定义了 PrintHierarchy 方法。
Level1
有一个 PrintHierarchy 方法。它并没有停止拥有 PrintHierarchy 方法,因此它绝不违反开放/封闭原则。
现在,更重要的是:将“PrintHierarchy”重命名为“Foo”。 Level2 是否遵循或违反了开放/封闭原则?
答案是我们一无所知,因为我们不知道“Foo”的语义是什么。因此,我们不知道是否应该在方法体的其余部分之后调用 base.Foo,在其余部分之前,在它的中间,还是根本不调用。
1.ToString()
应该返回“System.ObjectSystem.ValueType1”或“1System.ValueTypeSystem.Object”以在打开/关闭时保持这种伪装,还是应该在返回“1”之前将调用 base.ToString()
分配给未使用的变量“?
显然这些都不是。它应该尽可能返回有意义的字符串。它的基类型尽可能返回一个有意义的字符串,并且扩展它不会从对其基的调用中受益。
打开/关闭原则意味着,当我调用 Foo() 时,我期望发生一些 Fooing,并且当我在 Level1 上调用它时,我期望一些 Level1 适当的 Fooing,当我在 Level2 上调用它时,我期望一些 Level2 适当的 Fooing。 Level2 Fooing 是否应该涉及一些 Level1 Fooing 也取决于 Fooing 是什么。
base
是帮助我们扩展类的工具,而不是必需的。
【讨论】:
【参考方案2】:您无法仅通过静态查看系统(或类)来决定它是否遵循 OCP,没有上下文信息。
只有知道设计有哪些可能的变化,你才能判断它是否遵循OCP的这些特定变化。
没有任何语言结构可以帮助您。
一个好的启发式方法是使用某种指标,例如 Robert Martin 的 instability and abstractness (pdf),或缺乏凝聚力的指标,以便更好地为您的系统准备变化,并有更好的机会遵循 OCP 和 OOD 的所有其他重要原则。
【讨论】:
【参考方案3】:OCP 是关于前置条件和后置条件的:“当你只能用一个较弱的前置条件替换它的前置条件,而用一个更强的前置条件替换它的后置条件时”。 我不认为在重写方法中调用 base 会违反它。
【讨论】:
以上是关于抽象方法和开闭原则的主要内容,如果未能解决你的问题,请参考以下文章