何时使用特质,而不是继承和组合?


9

关于OOP,有三种常见的方法AFAIK实现可重用性

  1. 继承:通常代表is-一种关系(鸭子is-鸟)
  2. 组成:通常代表有一个关系(汽车有一个引擎)
  3. 特性(例如PHP中的trait关键字):...对此不确定

虽然在我看来特质可以实现has-a和is-a关系,但我真的不确定它打算用于哪种建模。特征是针对哪种情况设计的?


您可以对特质进行更多的解释,但是特质可以只是作词的另一个词吗?
Trilarion '16


1
我也很想知道为什么。没有使用过一次。
安迪

Answers:


6

性格是作曲的另一种方式。可以将它们视为在编译时(或JIT编译时)组成类的所有部分的一种方法,以组装所需部分的具体实现。

基本上,当您发现自己制作具有不同功能组合的类时,便希望使用特征。这种情况最经常出现在人们编写灵活的库供他人使用的情况下。例如,这是我最近使用ScalaTest编写的单元测试类的声明:

class TestMyClass
  extends WordSpecLike
  with Matchers
  with MyCustomTrait
  with BeforeAndAfterAll
  with BeforeAndAfterEach
  with ScalaFutures

单元测试框架有一个的不同的配置选项,每一个团队都有关于他们如何想要做的事情不同的喜好。通过将选项放入特征(with在Scala 中混合使用),ScalaTest可以提供所有这些选项,而无需创建类名(如WordSpecLikeWithMatchersAndFutures)或创建大量运行时布尔标志(如)WordSpecLike(enableFutures, enableMatchers, ...)。这使得遵循开放/封闭原则变得容易。您只需添加新特征即可添加新功能和功能的新组合。这也使遵循接口隔离原则变得更加容易,因为您可以轻松地将普遍不需要的功能放入特征中。

特性也是将通用代码放入几个类的共享共享继承层次结构的一种好方法。继承是一个紧密耦合的关系,如果可以帮助的话,您不应该为此付出代价。特质是一种松散耦合的关系。在上面的示例中,我曾经MyCustomTrait在几个其他不相关的测试类之间轻松共享模拟数据库实现。

依赖注入实现了许多相同的目标,但是在运行时基于用户输入,而不是在编译时基于程序员输入。特性也更多地用于语义上属于同一类的依赖项。您是在组装一个班级的各个部分,而不是召集其他负责任的班级。

依赖项注入框架根据程序员的输入在编译时实现了许多相同的目标,但是对于没有适当特征支持的编程语言,很大程度上是一种变通方法。特性将这些依赖项以更简洁的语法和更简单的构建过程带入了编译器类型检查器的领域,从而使编译时依赖项和运行时依赖项之间的区别更加清晰。


嗨,这是一个很好的答案。请问如何决定何时使用特征以及何时使用依赖项注入。似乎可以达到相同的效果。
Extrakun 16-3-22

敏锐的观察。看到我的编辑。
Karl Bielefeldt

感谢您撰写本文。我很好奇您对特征如成分@KarlBielefeldt的回答。您的类可以在需要该特性的任何地方使用,并且仍然会遭受与常规接口相同的脆弱性。所以看起来更接近子类型化和继承了,不是吗?我也不确定它与DI的相似性...如果您定义了各种扩展特征的类,那么不是在定义该类的层次结构中的任何地方(而不是在类的顶部/入口点)定义了这些具体的依赖项码?谢谢!
ALLSTAR

特性可以像接口一样用于其继承属性,但这并不是使它们唯一的原因。该with运营商有什么区别他们,with是一个复合操作。是的,特征可以替换DI,而无需在代码的入口点进行定义。在我看来,这是使它们更受欢迎的原因之一。
Karl Bielefeldt
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.