【问题标题】:OO principle: c#: design to interface and not concrete classesOO原则:c#:设计接口而不是具体类
【发布时间】:2011-11-04 22:03:41
【问题描述】:

我对使用具体类和接口的影响有一些疑问。

  1. 说一些代码(称之为chunkCode)使用具体类A。我是否必须重新编译chunkCode 如果:

    1. 我给A添加了一些新的公共方法?如果是这样,那是不是有点奇怪?毕竟我还是提供了chunkCode所依赖的接口。 (或者我是否必须重新编译,因为 chunkCode 可能永远不会知道这是真的,而且我没有省略一些 API)
    2. 我给A添加了一些新的私有方法?
    3. 我向A添加了一个新的公共字段?
    4. 我向A添加了一个新的私有字段?
  2. 工厂设计模式: 主代码不关心对象的具体类型是什么。它仅依赖于 API。但是,如果很少有方法只与一种具体类型相关,你会怎么做?这种类型实现了接口但增加了一些公共方法?您会在主代码中使用一些if (A is type1) 语句(或类似语句)吗?

感谢您的澄清

【问题讨论】:

  • 您能添加一些示例代码吗?一般来说,如果一个接口没有改变,你就不需要重新编译,但是如果你正在处理具体的类,你就需要。
  • 哈哈,大家都在编辑这篇文章。我更喜欢项目符号列表...

标签: oop design-patterns factory


【解决方案1】:

1) 在 OO 中编译不是一项活动。它是特定 OO 实现的细节。如果您想获得特定实现(例如 Java)的答案,那么您需要澄清。

一般来说,有些人会说添加到界面不被视为重大更改,而其他人则说一旦发布界面就无法更改界面,您必须创建新界面。

编辑:您指定了 C#,因此请查看 this question regarding breaking changes in .Net。我不想这样回答有害,所以我不会尝试在这里复制它。

2)人们经常修改他们的设计来做到这一点,但这表明你的设计很糟糕。

不错的选择

  • 在您的界面中创建一个方法,允许您调用自定义行为,但不需要知道该行为是什么。

  • 创建一个支持新方法的附加接口(和一个新工厂)。新接口不必继承旧接口,但如果有意义(如果接口之间可以表达 is-a 关系),则可以。

  • 如果您的语言支持,请使用Abstract Factory pattern,并在混凝土工厂中使用Covariant Return Types。如果您需要特定的派生类型,请接受具体工厂而不是抽象工厂。

糟糕的替代品(反模式):

  • 向接口添加一个在其他派生类中不执行任何操作的方法。

  • 在对派生类没有意义的方法中引发异常。

  • 向界面添加查询方法,告诉用户他们是否可以调用某个方法。

除非方法名称足够通用以至于用户不会期望它做任何事情(例如DoExtraProcessing),否则在大多数派生类中添加一个无操作的方法会破坏该接口定义的约定。

例如:调用bird.Fly() 的人会期望它实际做某事。我们知道鸡不会飞。所以Chicken 不是Bird,或者Birds 不是Fly

添加查询方法是一个糟糕的解决方法。例如。在您的界面中添加boolean CanFly() 方法或属性。抛出异常也是如此。他们都没有绕过类型根本不可替代的事实。查看Liskov Substitution Principle (LSP)。

【讨论】:

  • 我上面列出的反模式的一个值得注意的例外是Freezable Pattern。在这种情况下,您需要确保返回的接口/基类是可冻结的,并且工厂的文档说明它始终返回已冻结或未冻结的实例。
  • 1) 我指的是c#。那么谁是对的(关于 c#):0verbose 还是 celavek?
  • 能否请您详细说明:“如果您的语言支持……抽象一个。”是否与以下想法相同:“创建一个额外的接口......在接口之间)。”
  • @Elad:不,您不创建新接口(对于正在构造的类型)。您创建一个新的工厂接口,并从它派生。然后更改具体工厂中的签名以返回具体类型。这仅在您的语言支持协变返回类型时才有效。 C++ 支持这一点,但我不认为 C# 没有一些变通方法(例如,显式接口实现转发到具有不同返回类型的 new 方法)。
  • @Elad:至于 #1,我将链接到关于 .Net 重大变化的更完整的问题
【解决方案2】:

问题 1:取决于您在说什么语言。不过,重新编译这两种语言总是更安全。主要是因为chuckCode不知道A内部实际存在什么。重新编译会刷新它的内存。但它应该在 Java 中工作而无需重新编译。

问题 2:不。编写工厂的全部目的是摆脱 if(A is type1)。从维护的角度来看,这些 if 语句很糟糕。

Factory 旨在构建类似类型的对象。如果您遇到使用此语句的情况,则该对象与其他类的类型不同。如果您确定它是相似的类型并且具有相似的接口。我会在所有具体的基类中编写一个额外的函数,并只在这个基类上实现它。

理想情况下,所有这些具体类都应该有一个通用的抽象基类或接口来定义 API 是什么。除非您正在编写采用此特定类的函数,否则不应期望在代码中的任何位置调用此接口中设计的任何其他内容。

【讨论】:

  • "重新编译会刷新内存。" 重新编译可能意味着清理上次编译的输出并再次编译,而不是刷新内存..
  • 我不是指物理内存。我的意思是它将重新建立类和函数位置。如果接口发生变化,这一点尤其必要。你至少得到一个编译错误。 Java 可能并非如此,但如果您查看 C++,函数的实际位置存储在代码中。
【解决方案3】:

对于您的第一个问题,您的所有观点的答案都是“否”。如果是这样,那么向后兼容将没有任何意义。仅当您停止 API 时,您才必须重新编译 chunkCode,即删除 chunkCode 正在使用的一些功能、更改调用约定、修改参数数量,这些事情 == 破坏性更改。

第二个我通常,但只有当我真的必须时,在这些情况下使用dynamic_cast

注意我的答案在 C++ 的上下文中是有效的;我刚刚看到这个问题与语言无关(这个时候有点累;如果它冒犯了任何人,我会删除答案)。

【讨论】:

  • 你会如何回应 0verbose 的回答? "如果chunkCode直接引用一个具体类是"
  • @Elad Benda 对于 C++,我认为我的回答是正确的。在 Java 的情况下,它也适用 - 在 Java 中,即使在添加到接口的情况下更改接口,也不必重新编译(请参阅stackoverflow.com/questions/6780899/…;接受的答案中提供的链接很好读)。但有时,在极端情况下,您确实需要重新编译,请参阅stackoverflow.com/questions/536971/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-07
  • 2011-05-01
  • 2015-09-28
  • 2010-09-10
相关资源
最近更新 更多