[scala] Scala 케이스 클래스 상속

Squeryl을 기반으로 한 응용 프로그램이 있습니다. 주로 복사 메서드를 사용하는 것이 편리하다는 것을 알기 때문에 모델을 케이스 클래스로 정의합니다.

엄격하게 관련된 두 가지 모델이 있습니다. 필드는 동일하고 많은 작업이 공통되며 동일한 DB 테이블에 저장됩니다. 그러나 두 경우 중 하나에서만 의미가 있거나 두 경우 모두 의미가 있지만 다른 동작이 있습니다.

지금까지는 모델 유형을 구분하는 플래그가있는 단일 케이스 클래스 만 사용했으며 모델 유형에 따라 다른 모든 메소드는 if로 시작합니다. 이것은 성 가시고 형식이 안전하지 않습니다.

내가하고 싶은 것은 조상 케이스 클래스의 공통 동작과 필드를 고려하여 두 개의 실제 모델이 상속되도록하는 것입니다. 그러나 내가 이해하는 한, 케이스 클래스에서 상속하는 것은 Scala에서 눈살을 찌푸리고 하위 클래스 자체가 케이스 클래스 (내 경우가 아님) 인 경우에도 금지됩니다.

케이스 클래스에서 상속 할 때 알아야 할 문제점과 함정은 무엇입니까? 제 경우에 그렇게하는 것이 합리적입니까?



답변

코드 중복없이 케이스 클래스 상속을 피하는 선호하는 방법은 다소 분명합니다. 공통 (추상) 기본 클래스를 만듭니다.

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


답변

이것은 많은 사람들에게 흥미로운 주제이므로 여기에서 약간의 빛을 비추도록하겠습니다.

다음 접근 방식으로 갈 수 있습니다.

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

작곡은 또한 고려해야 할 유효하고 건전한 전략입니다.

봉인 된 특성이 무엇을 의미하는지 궁금한 경우-동일한 파일에서만 확장 할 수 있습니다. 즉, 위의 두 케이스 클래스는 동일한 파일에 있어야합니다. 이를 통해 완전한 컴파일러 검사가 가능합니다.

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사람들 (사람) 을 다루는 것을 잊지 않을 것 입니다. 이것은 본질적 Option으로 Scala 의 클래스가하는 일입니다.

그것이 중요하지 않다면 봉인되지 않은 상태로 만들고 케이스 클래스를 자체 파일에 던질 수 있습니다. 그리고 아마도 작곡과 함께 가십시오.


답변

케이스 클래스는 값 객체, 즉 속성을 변경하지 않고 같음과 비교할 수있는 객체에 적합합니다.

그러나 상속이있는 상태에서 동등을 구현하는 것은 다소 복잡합니다. 두 가지 클래스를 고려하십시오.

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)와 같지 않아야합니다. 색상이 다르기 때문입니다. 자, 평등 관계의 한 가지 기본 속성이 깨졌습니다.

최신 정보

다른 답변에서 설명한 것처럼 많은 문제를 해결하는 특성에서 상속을 사용할 수 있습니다. 더 유연한 대안은 종종 유형 클래스를 사용하는 것입니다. Scala의 유형 클래스는 무엇에 유용합니까?를 참조하십시오 . 또는 http://www.youtube.com/watch?v=sVMES4RZF-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")
}

분명히 더 정교한 계층 구조와 일치를 사용할 수 있지만 이것이 아이디어를 제공하기를 바랍니다. 핵심은 케이스 클래스가 제공하는 중첩 추출기를 활용하는 것입니다.


답변