Pewarisan kelas kasus Scala

89

Saya memiliki aplikasi berdasarkan Squeryl. Saya mendefinisikan model saya sebagai kelas kasus, terutama karena saya merasa nyaman untuk memiliki metode salin.

Saya memiliki dua model yang sangat terkait. Field-field tersebut sama, banyak operasi yang sama, dan mereka akan disimpan dalam tabel DB yang sama. Tetapi ada beberapa perilaku yang hanya masuk akal dalam salah satu dari dua kasus, atau yang masuk akal dalam kedua kasus tetapi berbeda.

Sampai saat ini saya hanya menggunakan satu kelas kasus, dengan tanda yang membedakan jenis model, dan semua metode yang berbeda berdasarkan jenis model dimulai dengan jika. Ini menjengkelkan dan tidak cukup aman.

Yang ingin saya lakukan adalah memfaktorkan perilaku dan bidang umum di kelas kasus leluhur dan memiliki dua model aktual yang diwarisi darinya. Tapi, sejauh yang saya pahami, mewarisi dari kelas kasus tidak disukai di Scala, dan bahkan dilarang jika subkelas itu sendiri adalah kelas kasus (bukan kasus saya).

Apa masalah dan perangkap yang harus saya waspadai dalam mewarisi dari kelas kasus? Apakah masuk akal dalam kasus saya untuk melakukannya?

Andrea
sumber
1
Tidak bisakah Anda mewarisi dari kelas non-kasus, atau memperluas sifat umum?
Eduardo
Saya tidak yakin. Bidang ditentukan di leluhur. Saya ingin mendapatkan metode penyalinan, kesetaraan dan sebagainya berdasarkan bidang tersebut. Jika saya mendeklarasikan induk sebagai kelas abstrak dan anak-anak sebagai kelas kasus, apakah itu akan memperhitungkan parameter akun yang ditentukan pada induk?
Andrea
Saya kira tidak, Anda harus mendefinisikan props di kedua abstrak parent (atau trait) dan kelas kasus target. Pada akhirnya, lot's o 'boilerplate, tapi ketikkan aman setidaknya
virtualeyes

Jawaban:

119

Cara yang saya sukai untuk menghindari pewarisan kelas kasus tanpa duplikasi kode agak jelas: buat kelas dasar umum (abstrak):

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


Jika Anda ingin lebih detail, kelompokkan properti menjadi ciri-ciri individu:

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
Malte Schwerhoff
sumber
83
Di manakah "tanpa duplikasi kode" yang Anda bicarakan? Ya, kontrak ditentukan antara kelas kasus dan induknya, tetapi Anda masih mengetik props X2
virtualeyes
5
@virtualeyes Benar, Anda masih harus mengulangi properti. Anda tidak perlu mengulangi metode, yang biasanya berjumlah lebih banyak kode daripada properti.
Malte Schwerhoff
1
ya, hanya berharap untuk mengatasi duplikasi properti - jawaban lain mengisyaratkan kelas tipe sebagai solusi yang mungkin; tidak yakin bagaimana, bagaimanapun, tampaknya lebih diarahkan pada pencampuran dalam perilaku, seperti sifat, tetapi lebih fleksibel. Hanya boilerplate re: kelas kasus, dapat hidup dengannya, akan sangat luar biasa jika sebaliknya, benar-benar dapat meretas petak besar definisi properti
virtualeyes
1
@virtualeyes Saya sepenuhnya setuju bahwa alangkah baiknya jika pengulangan properti dapat dihindari dengan cara yang mudah. Sebuah plugin compiler pasti bisa melakukan triknya, tapi saya tidak akan menyebutnya sebagai cara yang mudah.
Malte Schwerhoff
13
@virtualeyes Saya pikir menghindari duplikasi kode tidak hanya tentang menulis lebih sedikit. Bagi saya, ini lebih tentang tidak memiliki bagian kode yang sama di berbagai bagian aplikasi Anda tanpa ada koneksi di antara mereka. Dengan solusi ini, semua sub-kelas terikat ke kontrak, jadi jika kelas induk berubah, IDE akan dapat membantu Anda mengidentifikasi bagian-bagian kode yang perlu Anda perbaiki.
Daniel
40

Karena ini adalah topik yang menarik bagi banyak orang, izinkan saya menjelaskan sedikit di sini.

Anda bisa menggunakan pendekatan berikut:

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

Ya, Anda harus menduplikasi bidang. Jika tidak, tidak mungkin menerapkan kesetaraan yang benar di antara masalah lainnya .

Namun, Anda tidak perlu menduplikasi metode / fungsi.

Jika duplikasi beberapa properti terlalu penting bagi Anda, gunakan kelas reguler, tetapi ingat bahwa mereka tidak cocok dengan FP.

Alternatifnya, Anda bisa menggunakan komposisi alih-alih pewarisan:

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

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

Komposisi adalah strategi yang valid dan bagus yang harus Anda pertimbangkan juga.

Dan jika Anda bertanya-tanya apa artinya sifat tersegel - itu adalah sesuatu yang hanya dapat diperpanjang dalam file yang sama. Artinya, kedua kelas kasus di atas harus berada dalam file yang sama. Ini memungkinkan untuk pemeriksaan kompiler yang lengkap:

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

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

Memberikan kesalahan:

warning: match is not exhaustive!
missing combination            Tourist

Yang sangat berguna. Sekarang Anda tidak akan lupa untuk berurusan dengan tipe Personorang lain. Ini pada dasarnya adalah apa yang dilakukan Optionkelas di Scala.

Jika itu tidak menjadi masalah bagi Anda, maka Anda dapat membuatnya tidak tersegel dan memasukkan kelas kasus ke dalam file mereka sendiri. Dan mungkin pergi dengan komposisi.

Kai Sellgren
sumber
1
Saya pikir def namesifat itu perlu val name. Kompiler saya memberi saya peringatan kode yang tidak dapat dijangkau dengan yang sebelumnya.
BAR
13

kelas case sempurna untuk objek nilai, yaitu objek yang tidak mengubah properti apa pun dan dapat dibandingkan dengan persamaan.

Tetapi menerapkan yang setara dengan adanya warisan agak rumit. Pertimbangkan dua kelas:

class Point(x : Int, y : Int)

dan

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

Jadi menurut definisi ColorPoint (1,4, merah) harus sama dengan Titik (1,4), mereka adalah Titik yang sama. Jadi ColorPoint (1,4, biru) juga harus sama dengan Titik (1,4), bukan? Tapi tentu saja ColorPoint (1,4, merah) tidak boleh sama dengan ColorPoint (1,4, biru), karena warnanya berbeda. Ini dia, satu properti dasar dari hubungan kesetaraan rusak.

memperbarui

Anda dapat menggunakan warisan dari sifat untuk memecahkan banyak masalah seperti yang dijelaskan dalam jawaban lain. Alternatif yang lebih fleksibel adalah sering menggunakan kelas tipe. Lihat Apa kegunaan kelas tipe di Scala? atau http://www.youtube.com/watch?v=sVMES4RZF-8

Jens Schauder
sumber
Saya mengerti dan setuju dengan itu. Jadi, apa yang Anda sarankan harus dilakukan ketika Anda memiliki aplikasi yang berhubungan dengan, katakanlah, majikan dan karyawan. Asumsikan bahwa mereka berbagi semua bidang (nama, alamat, dan sebagainya), satu-satunya perbedaan adalah dalam beberapa metode - misalnya seseorang mungkin ingin mendefinisikan Employer.fire(e: Emplooyee)tetapi tidak sebaliknya. Saya ingin membuat dua kelas yang berbeda, karena mereka sebenarnya mewakili objek yang berbeda, tetapi saya juga tidak menyukai pengulangan yang muncul.
Andrea
mendapat contoh pendekatan kelas tipe dengan pertanyaan di sini? yaitu dalam hal kelas kasus
virtualeyes
@virtualeyes Seseorang dapat memiliki tipe yang sepenuhnya independen untuk berbagai jenis Entitas dan menyediakan Kelas Tipe untuk menyediakan perilakunya. Jenis Kelas ini bisa menggunakan warisan sebanyak berguna, karena mereka tidak terikat oleh kontrak kelas kasus semantik. Akankah berguna dalam pertanyaan ini? Tidak tahu, pertanyaannya tidak cukup spesifik untuk diceritakan.
Jens Schauder
@JensSchauder akan tampak bahwa ciri-ciri memberikan hal yang sama dalam hal perilaku, hanya kurang fleksibel dibandingkan kelas tipe; Saya mendapatkan di non-duplikasi properti kelas kasus, sesuatu yang sifat atau kelas abstrak biasanya akan membantu seseorang untuk menghindari.
virtualeyes
7

Dalam situasi ini saya cenderung menggunakan komposisi daripada pewarisan yaitu

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

Jelas Anda dapat menggunakan hierarki dan kecocokan yang lebih canggih tetapi mudah-mudahan ini memberi Anda gambaran. Kuncinya adalah memanfaatkan ekstraktor bersarang yang disediakan kelas kasus


sumber
3
Tampaknya ini menjadi satu-satunya jawaban di sini yang benar-benar tidak memiliki bidang duplikat
Alan Thomas