4

好的。我对 OO 和库设计的某些方面有一些疑问。

  1. 图书馆应该自给自足吗?例如,它可以使用外部依赖注入框架,还是应该以更轻量级的方式实现它自己?

  2. Liskov 的替换原则如何适用于知道方法或类行为的多态性?您只是期望它按应有的方式工作吗?

  3. 在源代码部分,将接口与实现分开放置在一个单独的文件夹(例如,/interfaces)中是一个坏习惯吗?

  4. where T : type在接口上而不是在它们的实现中分隔泛型类型 () 也是一个坏习惯?(这个我不这么认为,只是为了确认一下)。

  5. 当对象关系是“可以做”和“是”时,接口是否优于抽象类,并且不需要方法等的默认实现?

而已。谢谢你的时间=)

4

5 回答 5

3

1.图书馆应该自给自足吗?

库当然可以使用,例如,依赖注入框架。但是,是否要将 DI 框架强加于库的用户是有争议的。

2. Liskov 的替换原则如何适用于多态性?

Liskov 的替换原则是关于能够将一种实现与另一种实现互换(替换),并且无论行为如何,用户遵守合同时都不应出现错误。例如,如果您只使用类中的方法,将 a 换成CanonPrinteranEpsonPrinter仍然应该允许您打印Printer。调用某些东西Printer但有一个特定的实现(佳能,爱普生)是多态性。

3. 将接口与实现分开放在一个文件夹中是一个坏习惯吗?

您是否希望将接口与其实现分开,这是个人偏好。我不这样做;我什至没有每个实现的接口。我认为只有为您的项目增加价值的实现接口才有意义。

4. 在接口上限制泛型类型而不仅仅是在它们的实现中也是一个坏习惯吗?

如果您认为泛型类型应该限制为特定类型,那么您应该在有意义的地方这样做(因此,这可能在接口上)。例如,拥有 aIPrintableDocument<TPrinter>并且不限TPrinterPrinter对象是没有意义的,所以我会这样做。

5. 当对象关系既是“can do”又是“is a”时,接口是否优于抽象类?

确实,大多数人使用抽象类来表示可做关系,而接口则表示可以做关系。原因:一个类只能从一个基类继承,但可以从多个接口继承。从本质上讲,这意味着一个类只能是一件事(aEmployee 是 a Person),但可以做多个(它可能ICopyDocuments、、、IWalkAboutIMakeCoffee。当一个界面同时存在时,无论您做什么都取决于您的偏好。


您的大多数问题都与类(或接口)的契约有关:契约指定用户(另一个类,其他人的代码)可以对类及其成员做什么和不能做什么,并且它指定用户做什么可能期望类会做不会做

例如:用户只有在匹配参数类型时才能将对象作为参数传递给方法。该类只会将对象作为与返回类型匹配的返回值返回。用户只能调用类中定义的成员,并且类确保这些方法按照约定行事(即不抛出错误、无副作用、永不返回 null)。

泛型类型约束也是契约的一部分。我什至考虑合同的文档部分:当它声明一个方法永远不会抛出异常时,那么任何实现都不应该抛出异常。没有强制执行它的语法或编译器规则,但它是合同的一部分。合同的某些部分由编译器或运行时强制执行,而其他部分则不是。

当一个类或接口有一个特定的契约时,任何子类或实现都可以代替它,并且当该子类或实现遵守契约时它仍然可以工作。如果它遵守合同,那就是 Liskov 的替代原则 (LSP)。并且很容易偏离它,许多程序员都会这样做。有时你别无选择。

.NET Framework 中违反 LSP 的一个明显示例是ReadOnlyCollection<T>。它实现了IList<T>接口,但它的许多方法没有实际实现。因此,如果您传递一个期望 aIList<T>ReadOnlyCollection<T>用户并且该用户尝试调用list.Add,那么将引发异常。因此,您不能总是替换IList<T>ReadOnlyCollection<T>因此它违反了 LSP。

于 2012-08-02T19:29:09.980 回答
1

我将尝试按顺序回答这些问题:

  1. 这取决于。承担外部依赖可能会限制库的有用性和范围,因为它仅在该依赖可用的情况下可用。但是,这可能很好,并且优先于尝试解决依赖关系。如果您知道您的库的用户将始终使用特定的 DI 框架,我会围绕它构建库,而不是实现需要“适应”才能工作的东西。

  2. “你不知道方法或类的行为?” 您应该知道您正在使用的任何类型的合同。LSP 基本上说,无论你得到什么具体类型,基类/接口契约总是有效的。这意味着您可以只“使用类型”并期望它能够工作。

  3. 不——但这是一个私有的实现细节。然而,分离命名空间通常是个坏主意,而且保持源代码树与命名空间匹配通常是件好事。

  4. 不,如果一个接口真的打算只用于特定类型,它应该在接口上有约束。

  5. 一般来说,一份合同应该只提供关系的一个方面。如果合约建议“is-a”“can-do”,它可能应该分成两个合约,也许是一个实现“can-do”接口的抽象类。

于 2012-08-02T19:26:10.847 回答
0

在 #1 - http://jeviathon.com/2012/03/05/roll-your-own-syndrome/

基本上,关键是,当有人可能已经创建了功能强大、经过测试和支持的软件来做同样的事情时,为什么还要“推出你自己的”xyz 框架。

花时间研究选择要使用的“正确”外部库和框架,而不是重新发明轮子。

于 2012-08-02T19:40:07.063 回答
0
  1. “这取决于”。但是重新发明自己的轮子没有什么价值,尤其是在像 DI 和 ORM 这样人口密集(且难以实施)的领域。尤其是当您可以(a)bin-deploy 您的依赖项或(b)将您的依赖项指定为 nuget 包时,如果您的库是 nuget 包(当然应该如此)

  2. LSP 与多态性关系不大。它指出类(或方法)应该按照约定行事,这实际上是您唯一知道的关于实现者行为的事情。

  3. 文件夹应与命名空间相对应。您是否将接口放在单独的命名空间中?我没有,而且我认为没有人应该这样做。当我只有一个实现的接口时(例如,单元测试诱导的),我更喜欢将它的实现保存在一个文件中。

  4. 不,这不对。为什么应该这样?

  5. 如果不需要默认实现,请获取接口。接口更容易模拟,也更容易实现,因为它不会干扰你的实现的继承。实际上,您甚至可以在一个类上实现多个接口,如果这以某种方式突出了您的意图(例如,如果您有处理安全性的特定服务,那么实现IAuthentication和的情况并不少见IAuthorization)。最近我只在明确需要抽象类时才选择它们。

于 2012-08-02T19:40:49.487 回答
0

我会在5点刺伤。

抽象类和接口之间的唯一区别是抽象类可以具有类变量。如果抽象类仅由虚拟方法组成,我会选择一个接口。

于 2012-08-02T19:25:45.637 回答