Mixins vs Scala中的成分


78

在Java世界中(更确切地说,如果您没有多个继承/混合),经验法则非常简单:“在类继承上是最喜欢的对象组成”。

我想知道是否/如果您也考虑混入,特别是在scala中,如何更改混入?
是否将mixin视为一种多重继承或更多类组合的方式?
还有“从对象组成到从类组成”(或相反)的指导原则吗?

当人们使用(或滥用)mixins进行对象组合也可以完成工作时,我已经看到了很多示例,但我并不总是确定哪一个更好。在我看来,您可以通过它们实现非常相似的事情,但是也存在一些差异,例如:

  • 可见性-使用mixins时,所有内容都成为公共api的一部分,对于组合而言,情况并非如此。
  • 冗长-在大多数情况下,mixin不太冗长,使用起来也更容易一些,但并非总是如此(例如,如果您还在复杂的层次结构中使用自身类型)

我知道简短的答案是“取决于情况”,但是当这种情况更好时,可能会有一些典型情况。

到目前为止,我可以提出一些准则示例(假设我具有A和B的两个特征,A希望使用B的某些方法):

  • 如果要使用B的方法扩展A的API,则使用混合包,否则使用组合。但是,如果我正在创建的类/实例不属于公共API的一部分,则无济于事。
  • 如果您想使用一些需要混合的模式(例如Stackable Trait Pattern),那么这是一个简单的决定。
  • 如果您有循环依赖项,那么使用self类型的mixin可以提供帮助。(我试图避免这种情况,但这并不总是那么容易)
  • 如果您需要一些动态的,运行时的决定,然后决定如何进行合成,然后进行对象合成。

在许多情况下,mixin似乎更容易(和/或不太冗长),但是我敢肯定,它们也存在一些陷阱,例如“上帝类”以及其他两篇artima文章中所述的其他内容:第1部分第2部分(顺便说一句在我看来,其他大多数问题与scala无关/并不那么严重。

您还有更多类似的提示吗?

Answers:


41

如果只将抽象特征混入类定义中,然后在对象实例化时混入相应的具体特征,则在Scala中可以避免人们所遇到的许多混合问题。例如

trait Locking{
   // abstract locking trait, many possible definitions
   protected def lock(body: =>A):A
}

class MyService{
   this:Locking =>
}

//For this time, we'll use a java.util.concurrent lock
val myService:MyService = new MyService with JDK15Locking 

此构造有几件事值得推荐。首先,由于需要特征功能的不同组合,因此可以防止类爆炸。其次,它可以轻松进行测试,因为它可以创建并混入“不做任何事”的具体特征,类似于模拟对象。最后,我们已经从服务的使用者中完全隐藏了所使用的锁定特性,甚至锁定正在进行中。

由于我们已经克服了混入所声称的大多数缺点,因此我们仍然需要在混入和合成之间进行权衡。对于我自己,我通常根据一个假设的委托对象是否被包含的对象完全封装,或者是否有可能被共享并拥有自己的生命周期来做出决定。锁定为完全封装的委托提供了一个很好的例子。如果您的类使用锁对象来管理对其内部状态的并发访问,则该锁将完全由包含对象控制,并且该锁及其操作均不会作为该类的公共接口的一部分进行宣传。对于这样的完全封装的功能,我使用了混入。对于共享的东西,例如数据源,请使用组合。


构造的含义是this => Logging什么?它不会编译。
HRJ 2012年

3
这是一个自我类型的注释,我总是忘记它的语法。编辑。在这种情况下,这意味着扩展MyService的任何对象也必须扩展Locking
Dave Griffith

11

您尚未提及的其他差异:

  • 特质类没有任何独立的存在:

编程Scala

如果发现某个特定特征最常用作其他类的父特征,以使子类充当其父特征,则可以考虑将特征定义为一个类,以使这种逻辑关系更加清晰。
(我们说它的行为是,而不是a,因为前者是基于Liskov替代原理的更精确的继承定义-例如,请参见[Martin2003]。)

[Martin2003]:Robert C. Martin,敏捷软件开发:原理,模式和实践,Prentice-Hall,2003年

  • mixins(trait)没有构造函数参数。

因此 ,仍然来自Programming Scala建议

避免特征中无法初始化为适当默认值的具体字段。
改用抽象字段或用构造函数将特征转换为类
当然,无状态特征在初始化时没有任何问题。

良好的面向对象设计的一般原则是,从构造过程结束之时起,实例应始终处于已知的有效状态

关于对象的初始状态的最后一部分,通常有助于在给定概念的类(和类组成)和特征(和混合)之间做出决定。


感谢您的回答,但我认为更多是关于特征与类。在该主题上我感到更自在:-)也许当我使用“类组合”一词时不够精确。我的意思在Stackable Trait Pattern文章中进行了描述,“该模式在结构上与装饰器模式相似,不同之处在于它混合了用于类组合而不是对象组合的装饰”,尽管它混合了类和特征。
桑多·穆拉科齐

@桑多:我明白。我正在解决您问题的基本部分,因为我对这种区别还不完全满意,但是您很快就会得到更准确的答案。
VonC

mixin(trait)没有构造函数参数。 ”有趣的是,Dotty(成为Scala 3)支持特征参数。结果,您提到的Programming Scala的经验法则可能会演变。
jub0bs

1
@Jubobs谢谢。8年后,这个规则可能会演变,我并不感到惊讶。
VonC
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.