93

Squerylをベースにしたアプリケーションがあります。モデルをケースクラスとして定義します。これは主に、コピーメソッドがあると便利だからです。

厳密に関連する2つのモデルがあります。フィールドは同じであり、多くの操作が共通しており、同じDBテーブルに格納されます。ただし、2つのケースのいずれかでのみ意味がある、または両方のケースで意味があるが異なる動作がいくつかあります。

これまで、モデルのタイプを区別するフラグを持つ単一のケースクラスのみを使用してきました。モデルのタイプに基づいて異なるすべてのメソッドは、ifで始まります。これは煩わしく、タイプセーフではありません。

私がやりたいのは、祖先のケースクラスの一般的な動作とフィールドを因数分解し、2つの実際のモデルにそれを継承させることです。しかし、私が理解している限り、Scalaではケースクラスからの継承は嫌われており、サブクラス自体がケースクラス(私の場合ではない)である場合でも禁止されています。

ケースクラスから継承する際に注意すべき問題と落とし穴は何ですか?私の場合、そうすることは理にかなっていますか?

4

4 に答える 4

128

コードの重複なしにケースクラスの継承を回避するための私の好ましい方法は、いくぶん明白です。共通の(抽象)基本クラスを作成します。

abstract class Person {
  def name: String
  def age: Int
  // address and other properties
  // methods (ideally only accessors since it is a case class)
}

case class Employer(val name: String, val age: Int, val taxno: Int)
    extends Person

case class Employee(val name: String, val age: Int, val salary: Int)
    extends Person


よりきめ細かくしたい場合は、プロパティを個々の特性にグループ化します。

trait Identifiable { def name: String }
trait Locatable { def address: String }
// trait Ages { def age: Int }

case class Employer(val name: String, val address: String, val taxno: Int)
    extends Identifiable
    with    Locatable

case class Employee(val name: String, val address: String, val salary: Int)
    extends Identifiable
    with    Locatable
于 2012-10-03T10:21:23.237 に答える
44

これは多くの人にとって興味深いトピックなので、ここで少し光を当てましょう。

次のアプローチをとることができます。

// You can mark it as 'sealed'. Explained later.
sealed trait Person {
  def name: String
}

case class Employee(
  override val name: String,
  salary: Int
) extends Person

case class Tourist(
  override val name: String,
  bored: Boolean
) extends Person

はい、フィールドを複製する必要があります。そうしないと、他の問題の中で正しい平等を実装することは不可能になります。

ただし、メソッド/関数を複製する必要はありません。

いくつかのプロパティの重複が非常に重要である場合は、通常のクラスを使用しますが、それらはFPにうまく適合しないことに注意してください。

または、継承の代わりに構成を使用することもできます。

case class Employee(
  person: Person,
  salary: Int
)

// In code:
val employee = ...
println(employee.person.name)

作曲は有効で健全な戦略であり、同様に検討する必要があります。

また、封印された特性が何を意味するのか疑問に思われる場合は、同じファイルでのみ拡張できるものです。つまり、上記の2つのケースクラスは同じファイルに含まれている必要があります。これにより、徹底的なコンパイラチェックが可能になります。

val x = Employee(name = "Jack", salary = 50000)

x match {
  case Employee(name) => println(s"I'm $name!")
}

エラーが発生します:

warning: match is not exhaustive!
missing combination            Tourist

これは本当に便利です。Personこれで、他のタイプのs(人)に対処することを忘れないでください。これは基本的OptionにScalaのクラスが行うことです。

それが問題にならない場合は、それを非封印にして、ケースクラスを独自のファイルにスローすることができます。そして、おそらく作曲と一緒に行きます。

于 2013-10-14T11:18:17.477 に答える
14

ケースクラスは、値オブジェクト、つまりプロパティを変更せず、equalsと比較できるオブジェクトに最適です。

しかし、継承が存在する場合にequalsを実装するのはかなり複雑です。2つのクラスを考えてみましょう。

class Point(x : Int, y : Int)

class ColoredPoint( x : Int, y : Int, c : Color) extends Point

したがって、定義によれば、ColorPoint(1,4、red)はPoint(1,4)と等しくなければなりません。結局、それらは同じPointです。つまり、ColorPoint(1,4、blue)もPoint(1,4)と等しくなければなりませんね。ただし、もちろん、ColorPoint(1,4、red)はColorPoint(1,4、blue)と同じであってはなりません。これは、色が異なるためです。さあ、等式関係の基本的な性質の1つが壊れています。

アップデート

別の回答で説明されているように、多くの問題を解決する特性からの継承を使用できます。さらに柔軟な代替手段は、多くの場合、型クラスを使用することです。Scalaの型クラスは何に役立ちますか?を参照してください。またはhttp://www.youtube.com/watch?v=sVMES4RZF-8

于 2012-10-03T09:33:08.100 に答える
8

これらの状況では、私は継承の代わりに構成を使用する傾向があります。

sealed trait IVehicle // tagging trait

case class Vehicle(color: String) extends IVehicle

case class Car(vehicle: Vehicle, doors: Int) extends IVehicle

val vehicle: IVehicle = ...

vehicle match {
  case Car(Vehicle(color), doors) => println(s"$color car with $doors doors")
  case Vehicle(color) => println(s"$color vehicle")
}

明らかに、より洗練された階層と一致を使用できますが、うまくいけば、これによってアイデアが得られます。重要なのは、ケースクラスが提供するネストされたエクストラクタを利用することです。

于 2016-08-08T15:36:41.030 に答える