Scala durum sınıf kalıtımı


91

Squeryl'e dayalı bir uygulamam var. Modellerimi, çoğunlukla kopyalama yöntemlerine sahip olmayı uygun bulduğum için vaka sınıfları olarak tanımlıyorum.

Kesinlikle ilişkili iki modelim var. Alanlar aynıdır, birçok işlem ortaktır ve aynı DB tablosunda depolanmalıdır. Ancak iki durumdan yalnızca birinde anlamlı olan veya her iki durumda da anlamlı olan ancak farklı olan bazı davranışlar vardır.

Şimdiye kadar, modelin türünü ayırt eden bir bayrakla yalnızca tek bir durum sınıfı kullandım ve modelin türüne göre farklılık gösteren tüm yöntemler bir if ile başlar. Bu can sıkıcı ve pek güvenli değil.

Yapmak istediğim şey, bir ata durum sınıfındaki ortak davranış ve alanları faktörlere ayırmak ve iki gerçek modelin ondan miras almasını sağlamaktır. Ancak, anladığım kadarıyla, vaka sınıflarından miras almak Scala'da hoş karşılanmıyor ve alt sınıfın kendisi bir vaka sınıfı ise (benim durumum değil) bile yasak.

Bir vaka sınıfından miras alırken bilmem gereken sorunlar ve tuzaklar nelerdir? Benim durumumda bunu yapmak mantıklı mı?


1
Vakasız bir sınıftan miras alamaz veya ortak bir özelliği genişletemez misiniz?
Eduardo

Emin değilim. Alanlar atada tanımlanmıştır. Kopyalama yöntemleri, eşitlik ve benzeri alanlara dayalı olarak almak istiyorum. Ebeveyni soyut bir sınıf ve çocukları da bir vaka sınıfı olarak ilan edersem, ebeveynde tanımlanan parametreleri hesaba katar mı?
Andrea

Sanırım değil, hem soyut ebeveyn (veya özellik) hem de hedef vaka sınıfında sahne tanımlamanız gerekiyor. Sonunda, lotun standart metni, ancak en azından güvenli yazın
sanal gözler

Yanıtlar:


121

Kod yinelemesi olmadan durum sınıfı kalıtımından kaçınmanın tercih edilen yolu biraz açıktır: ortak (soyut) bir temel sınıf oluşturun:

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


Daha ayrıntılı olmak istiyorsanız, özellikleri ayrı özelliklere göre gruplandırın:

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

84
Bu bahsettiğiniz "kod kopyası olmadan" nerede? Evet, vaka sınıfı ile ebeveyn (ler) i arasında bir sözleşme tanımlandı, ancak yine de sahne X2 yazıyorsunuz
2012'de

6
@virtualeyes Doğru, yine de özellikleri tekrarlamanız gerekir. Yine de, genellikle özelliklerden daha fazla koda karşılık gelen yöntemleri tekrarlamanız gerekmez.
Malte Schwerhoff

1
evet, sadece özelliklerin kopyalanması üzerine çalışmayı umuyordum - başka bir cevap, olası bir geçici çözüm olarak tür sınıflarına dair ipuçları veriyor; bununla birlikte, özellikler gibi davranışları karıştırmaya nasıl daha uygun olduğundan emin değilim, ama daha esnek. Sadece standart yeniden: vaka sınıfları, onunla yaşayabilir, aksi takdirde oldukça inanılmaz olurdu, mülk tanımlarının büyük bir kısmını gerçekten ortadan kaldırabilirdi
virtualeyes

1
@virtualeyes Mülk tekrarından kolay bir şekilde kaçınılmasının harika olacağına tamamen katılıyorum. Bir derleyici eklentisi kesinlikle işe yarayabilir, ancak ben buna kolay bir yol demezdim.
Malte Schwerhoff

13
@virtualeyes Kod tekrarından kaçınmanın sadece daha az yazmakla ilgili olmadığını düşünüyorum. Benim için daha çok, uygulamanızın farklı bölümlerinde aralarında herhangi bir bağlantı olmadan aynı kod parçasına sahip olmamakla ilgili. Bu çözümle, tüm alt sınıflar bir sözleşmeye bağlanır, bu nedenle ana sınıf değişirse, IDE, kodun düzeltmeniz gereken kısımlarını tanımlamanıza yardımcı olabilir.
Daniel

40

Bu birçokları için ilginç bir konu olduğundan, burada biraz ışık tutmama izin verin.

Aşağıdaki yaklaşımla gidebilirsiniz:

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

Evet, alanları çoğaltmanız gerekiyor. Aksi takdirde, diğer problemler arasında doğru eşitliği uygulamak mümkün olmayacaktır .

Ancak, yöntemleri / işlevleri kopyalamanıza gerek yoktur.

Birkaç özelliğin kopyalanması sizin için bu kadar önemliyse, normal sınıfları kullanın, ancak bunların FP'ye iyi uymadığını unutmayın.

Alternatif olarak, miras yerine kompozisyon kullanabilirsiniz:

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

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

Beste, dikkate almanız gereken geçerli ve sağlam bir stratejidir.

Ve eğer mühürlü bir özelliğin ne anlama geldiğini merak ediyorsanız - bu, yalnızca aynı dosyada genişletilebilecek bir şeydir. Yani, yukarıdaki iki vaka sınıfı aynı dosyada olmalıdır. Bu, kapsamlı derleyici denetimlerine izin verir:

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

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

Bir hata verir:

warning: match is not exhaustive!
missing combination            Tourist

Bu gerçekten kullanışlı. Artık diğer tür Person(insanlar) ile uğraşmayı unutmayacaksınız . Bu esasen OptionScala'daki sınıfın yaptığı şeydir .

Bu sizin için önemli değilse, onu mühürsüz yapabilir ve vaka sınıflarını kendi dosyalarına atabilirsiniz. Ve belki kompozisyonla devam edin.


1
Ben def nameözelliğin olması gerektiğini düşünüyorum val name. Derleyicim bana eski ile ulaşılamaz kod uyarıları veriyordu.
BAR

13

case sınıfları değer nesneleri için mükemmeldir, yani herhangi bir özelliği değiştirmeyen ve eşitler ile karşılaştırılabilen nesneler.

Ancak miras varlığında eşittir uygulamak oldukça karmaşıktır. İki sınıf düşünün:

class Point(x : Int, y : Int)

ve

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

Dolayısıyla tanıma göre ColorPoint (1,4, kırmızı) Point (1,4) 'e eşit olmalıdır, sonuçta aynı Pointtir. Yani ColorPoint (1,4, mavi) da Nokta (1,4) 'e eşit olmalıdır, değil mi? Ama tabii ki ColorPoint (1,4, kırmızı) ColorPoint (1,4, mavi) ile aynı olmamalıdır çünkü farklı renkleri vardır. İşte eşitlik ilişkisinin temel bir özelliği bozuldu.

Güncelleme

Başka bir cevapta açıklandığı gibi birçok sorunu çözmek için özelliklerden kalıtımı kullanabilirsiniz. Daha da esnek bir alternatif, genellikle tür sınıflarını kullanmaktır. Scala'daki tür sınıfları neler için yararlıdır? Konusuna bakın. veya http://www.youtube.com/watch?v=sVMES4RZF-8


Bunu anlıyorum ve katılıyorum. Öyleyse, mesela işverenler ve çalışanlarla ilgili bir uygulamanız olduğunda ne yapılmasını önerirsiniz? Tüm alanları (ad, adres vb.) Paylaştıklarını varsayın, tek fark bazı yöntemlerde olabilir - örneğin biri tanımlamak isteyebilir, Employer.fire(e: Emplooyee)ancak tersi değil. Aslında farklı nesneleri temsil ettikleri için iki farklı sınıf yapmak istiyorum, ancak ortaya çıkan tekrardan da hoşlanmıyorum.
Andrea

Buradaki soruyla birlikte bir tür sınıf yaklaşımı örneği mi var? örn. vaka sınıflarıyla ilgili olarak
sanal gözler

@virtualeyes Biri, çeşitli Varlık türleri için tamamen bağımsız türlere sahip olabilir ve davranışı sağlamak için Tür Sınıfları sağlayabilir. Bu Tip Sınıfları, vaka sınıflarının anlamsal sözleşmesine bağlı olmadıkları için mirası yararlı olduğu kadar kullanabilir. Bu soruda faydalı olur mu? Bilmiyorum, soru söylenecek kadar spesifik değil.
Jens Schauder

@JensSchauder, özelliklerin davranış açısından aynı şeyi sağladığı görülüyor, tip sınıflarından daha az esnek; Vaka sınıfı özelliklerinin yinelenmemesine ulaşıyorum, bu özelliklerin veya soyut sınıfların normalde birinin kaçınmasına yardımcı olacağı bir şey.
virtualeyes

7

Bu durumlarda kalıtım yerine kompozisyon kullanma eğilimindeyim yani

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

Açıkçası daha sofistike bir hiyerarşi ve eşleşmeler kullanabilirsiniz, ancak umarım bu size bir fikir verir. Anahtar, vaka sınıflarının sağladığı iç içe yerleştirilmiş ayıklayıcılardan yararlanmaktır.


3
Burada gerçekten yinelenen alanlara sahip olmayan tek cevap bu gibi görünüyor
Alan Thomas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.