繁体   English   中英

把方法放在特质或案例类?

[英]Put method in trait or in case class?

有两种方法可以为Scala中继承相同特征的两个不同类定义方法。

sealed trait Z { def minus: String }
case class A() extends Z { def minus = "a" }
case class B() extends Z { def minus = "b" }

替代方案如下:

sealed trait Z { def minus: String = this match {
    case A() => "a"
    case B() => "b"
}
case class A() extends Z
case class B() extends Z

第一种方法重复方法名称,而第二种方法重复类名称。

我认为第一种方法最好使用,因为代码是分开的。 但是,我发现自己经常使用第二个方法来处理复杂的方法,因此可以非常容易地添加其他参数,例如:

sealed trait Z {
  def minus(word: Boolean = false): String = this match {
    case A() => if(word) "ant" else "a"
    case B() => if(word) "boat" else "b"
}
case class A() extends Z
case class B() extends Z

这些做法之间有什么其他差异? 如果我选择第二种方法,是否还有等待我的错误?

编辑:我引用了开放/封闭原则,但有时,我需要修改函数的输出,这取决于新的案例类,还需要修改因为代码重构的输入。 有没有比第一个更好的模式? 如果我想在第一个例子中添加前面提到的功能,这将产生重复输入的丑陋代码:

sealed trait Z { def minus(word: Boolean): String  ; def minus = minus(false) }
case class A() extends Z { def minus(word: Boolean) = if(word) "ant" else "a" }
case class B() extends Z { def minus(word: Boolean) = if(word) "boat" else "b" }

我会选择第一个。

为什么? 仅保持开放/封闭原则

实际上,如果你想添加另一个子类,让我们说case class C ,你将不得不修改supertrait / superclass来插入新的条件......丑陋

您的场景在Java中具有类似于模板/策略模式的条件

更新:

在您的上一个场景中,您无法避免输入的“重复”。 实际上,Scala中的参数类型是不可推断的。

最好是使用内聚方法而不是将整个内部混合在一个方法中,该方法提供与union期望的方法一样多的参数。

想象一下你的supertrait方法中的十个条件。 如果你无意中改变了每个人的行为怎么办? 每次更改都会有风险,每次修改时都应该运行supertrait单元测试...

此外,无意中改变输入参数(不是行为)根本不是“危险的”。 为什么? 因为编译器会告诉你参数/参数类型不再相关。 如果你想改变它并为每个子类做同样的事情...问你的IDE,它喜欢一键重构这样的事情。

正如此链接所解释的:

为什么开放式原则很重要:

无需单元测试。
无需了解重要且庞大的类的源代码。
由于绘图代码被移动到具体的子类,因此在添加新功能时会降低影响旧功能的风险。

更新2:

这里有一个避免输入重复的样本符合您的期望:

sealed trait Z { 
     def minus(word: Boolean): String = if(word) whenWord else whenNotWord
     def whenWord: String
     def whenNotWord: String             
  }

case class A() extends Z { def whenWord = "ant"; def whenNotWord = "a"}

谢谢类型推断:)

就个人而言,我会远离第二种方法。 每次添加Z的新子类时,都必须触摸共享的减号方法,这可能会使与现有实现相关的行为处于危险之中。 使用第一种方法添加新子类对现有结构没有潜在的副作用。 这里可能有一些开放/封闭原则,你的第二种方法可能会违反它。

两种方法都可能违反开放/封闭原则。 它们彼此正交。 第一个允许轻松添加新类型并实现所需的方法,如果您需要将新方法添加到层次结构或重构方法签名到破坏任何客户端代码的点,它会打破打开/关闭原则。 毕竟,为什么在Java8接口中添加了默认方法,以便可以扩展旧的API而无需客户端代码进行调整。 这种方法对于OOP来说是典型的。

FP的第二种方法更为典型。 在这种情况下,添加方法很容易,但很难添加新类型(这里打破了O / C)。 这是封闭层次结构的好方法,典型的例子是代数数据类型(ADT)。 不希望客户扩展的标准化协议可能是候选者。

语言很难设计出能够同时带来好处的API - 易于添加类型以及添加方法。 此问题称为表达式问题。 Scala提供了Typeclass模式来解决这个问题,它允许以临时和选择的方式向现有类型添加功能。

哪一个更好取决于您的用例。

请注意,使用DottyScala 3基础),您可以使用特征参数 (就像类具有参数一样),这在这种情况下简化了很多事情:

trait Z(x: String) { def minus: String = x }
case class A() extends Z("a")
case class B() extends Z("b")
A().minus // "a"
B().minus // "b"

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM