2012-10-03 12 views
69

私はSquerylに基づくアプリケーションを持っています。私はモデルをケースクラスとして定義しています。コピーメソッドを持つのが便利だからです。スカラのケースクラス継承

私は2つのモデルが厳密に関連しています。フィールドは同じで、多くの操作は共通で、同じDBテーブルに格納されます。 しかし、2つのケースのどちらかで意味をなさないか、どちらのケースでも意味がありますが、異なるケースがいくつかあります。

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

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

What are the problems and pitfalls I should be aware in inheriting from a case class? Does it make sense in my case to do so?

+1

ノンケースクラスから継承できないのでしょうか、共通の特性を拡張できませんでしたか? – Eduardo

+0

わかりません。フィールドは祖先で定義されています。私はそれらのフィールドに基づいてコピーメソッド、平等などを取得したい。親を抽象クラスとして宣言し、子をケースクラスとして宣言すると、親に定義されたパラメータを考慮に入れますか? – Andrea

+0

私は、抽象親(または形質)とターゲットケースクラスの両方で小道具を定義しなければならないと思います。結局、ロットは定型文ですが、少なくとも – virtualeyes

答えて

89

コードの重複なしケースクラスの継承を回避するための私の好ましい方法は、いくぶん明白です:あなたがよりきめ細かくなりたい場合は

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 


:共通(抽象)基本クラスを作成しますプロパティを個々の特性にグループ化してください:

+48

あなたが話すこの「コードの重複なし」はどこですか?はい、契約クラスはケースクラスとその親の間で定義されていますが、依然として小道具を入力しています。X2 – virtualeyes

+2

@virtualeyes True、それでもプロパティを繰り返す必要があります。ただし、メソッドを繰り返す必要はありませんが、通常はプロパティより多くのコードになります。 –

+1

ええ、プロパティの重複を回避することを望んでいました。もう1つの答えは、可能な回避策として型クラスを示唆しています。しかし、どのようにして、どのように特性のような振る舞いが混じっているように見えるのかは分かりませんが、より柔軟になります。もしそれがそうでなければ、本当にプロパティ定義の大きなスワスをハックすることができたと思うでしょう。 – virtualeyes

12

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

しかし、継承の存在下でequalsを実装することは、やや複雑です。彼らは同じポイントです(1,4)

class Point(x : Int, y : Int) 

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

だから、定義によれば(赤1,4、)ColorPointはポイントに等しいはずである:2つのクラスを考えてみましょう結局。だからColorPoint(1,4、青色)もPoint(1,4)と同じでなければなりません。もちろん、ColorPoint(1,4、赤色)はColorPoint(1,4、青色)と同じであってはなりません。そこに、平等関係の1つの基本的な特性が壊れています。

あなたは別の答えで述べたような問題の多くを解決する特性からの継承を使用することができます

更新。より柔軟な代替手段はしばしば型クラスを使用することです。参照してくださいWhat are type classes in Scala useful for?またはhttp://www.youtube.com/watch?v=sVMES4RZF-8

+0

を入力してください。私はそれを理解して同意します。それで、雇用者や従業員を扱うアプリケーションを持っているときには、何をすべきかを示唆しています。たとえば、Employer.fire(e:Emplooyee)を定義することができますが、それ以外の方法では定義できないなど、いくつかのメソッドには唯一の違いがあり、すべてのフィールド(名前、アドレスなど)を共有しているとします。実際には異なるオブジェクトを表現するので、2つの異なるクラスを作成したいと思いますが、その繰り返しが嫌いです。 – Andrea

+0

ここに質問があるクラスのアプローチの例がありますか?すなわち、ケースクラスに関しては、 – virtualeyes

+0

@virtualeyes Oneは様々な種類のエンティティに対して完全に独立した型を持ち、動作を提供する型クラスを提供することができます。これらの型クラスは、継承を有用なものとして使うことができます。なぜなら、それらがケースクラスのセマンティックコントラクトに束縛されていないからです。この質問に役立つだろうか?分かりませんが、質問は具体的なものではありません。 –

36

これは多くの人にとって興味深い話題なので、ここでいくつかの光を当てましょう。あなたは、次のアプローチで行くことができる

// 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 

はい、あなたはフィールドを複製する必要があります。そうでない場合は、正確な等価性を実装することはできません。among other problems

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

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

また、あなたの代わりに相続物の組成を使用することができます。

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

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

構成は、有効とあなたにも検討すべき音戦略です。

封印された形質が何を意味しているのだろうか。それは同じファイル内でのみ拡張できるものです。つまり、上の2つのクラスは同じファイルになければなりません。これは完全なコンパイラをチェックすることができます:本当に便利です

warning: match is not exhaustive! 
missing combination   Tourist 

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

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

はエラーになります。今度は他の種類のPerson(人)を扱うことを忘れないでください。これは本質的にScalaのクラスOptionの機能です。

これはあなたにとって重要でない場合は、それを非シールにして、ケースクラスを独自のファイルにスローすることができます。そして、おそらく構成と一緒に行く。このような状況で

+1

私は、その特性の中の 'def name 'は' val name'である必要があると思います。私のコンパイラは私に到達できないコードの警告を私に与えていました。 – BAR

3

私はもちろん、あなたがより洗練された階層と一致するものを使用することができます代わりに相続すなわち

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") 
} 

物の組成を使用する傾向があるが、うまくいけば、これはあなたのアイデアを提供します。キーは、ケースクラスが提供するネストされたエクストラクタを利用することです