Scala 케이스 클래스 상속


90

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

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

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

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

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


1
케이스가 아닌 클래스에서 상속하거나 공통 특성을 확장 할 수 없습니까?
에두아르도

잘 모르겠습니다. 필드는 조상에서 정의됩니다. 해당 필드를 기반으로 복사 방법, 동등성 등을 얻고 싶습니다. 부모를 추상 클래스로 선언하고 자식을 케이스 클래스로 선언하면 부모에 정의 된 매개 변수를 고려합니까?
안드레아

추상 부모 (또는 특성)와 대상 케이스 클래스 모두에서 소품을 정의해야한다고 생각합니다. 결국, lot 's o '상용구,하지만 적어도 안전한 타입
virtualeyes

답변:


120

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

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

84
당신이 말하는 "코드 중복없이"는 어디에 있습니까? 예, 계약은 케이스 클래스와 부모 사이에 정의되어 있지만 여전히 props X2를 입력하고 있습니다
virtualeyes

6
@virtualeyes 사실, 여전히 속성을 반복해야합니다. 그러나 일반적으로 속성보다 더 많은 코드에 해당하는 메서드를 반복 할 필요는 없습니다.
Malte Schwerhoff

1
네, 속성의 중복을 해결하기를 바랬습니다. 가능한 해결 방법으로 유형 클래스에 대한 또 다른 대답 힌트입니다. 그러나 특성과 같은 행동을 혼합하는 데 더 적합하게 보이는지 확실하지 않지만 더 유연합니다. 그냥 상용구 다시 : 케이스 클래스, 그것과 함께 살 수 있고, 그렇지 않다면 꽤 믿을 수 없을 것입니다. 정말 많은 속성 정의를 해킹 할 수 있습니다
virtualeyes

1
@virtualeyes 나는 재산 반복을 쉬운 방법으로 피할 수 있다면 좋을 것이라는 데 전적으로 동의합니다. 컴파일러 플러그인은 확실히 트릭을 할 수 있지만 쉬운 방법은 아닙니다.
Malte Schwerhoff

13
@virtualeyes 코드 중복을 피하는 것은 글을 적게 쓰는 것만이 아니라고 생각합니다. 저에게 있어서는 애플리케이션의 서로 다른 부분에서 서로 연결하지 않고 동일한 코드를 사용하지 않는 것입니다. 이 솔루션을 사용하면 모든 하위 클래스가 계약에 연결되므로 상위 클래스가 변경되면 IDE에서 수정해야하는 코드 부분을 식별하는 데 도움을 줄 수 있습니다.
다니엘

40

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

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

// 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 의 클래스가하는 일입니다.

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


1
나는 def name특성에 있어야 할 필요가 있다고 생각 합니다 val name. 내 컴파일러는 전자에 도달 할 수없는 코드 경고를 표시했습니다.
BAR

13

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

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

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


나는 그것을 이해하고 동의합니다. 따라서 고용주와 직원을 다루는 응용 프로그램이있을 때 무엇을 제안해야합니까? 모든 필드 (이름, 주소 등)를 공유한다고 가정합니다. 유일한 차이점은 일부 메서드에 있습니다 Employer.fire(e: Emplooyee). 실제로 다른 객체를 나타 내기 때문에 두 개의 다른 클래스를 만들고 싶지만 반복되는 반복도 싫어합니다.
Andrea

여기에 질문에 대한 유형 클래스 접근 방식의 예가 있습니까? 즉 케이스 클래스와 관련하여
virtualeyes

@virtualeyes One은 다양한 종류의 엔티티에 대해 완전히 독립적 인 유형을 가질 수 있으며 동작을 제공하는 유형 클래스를 제공 할 수 있습니다. 이러한 유형 클래스는 케이스 클래스의 의미 적 계약에 구속되지 않기 때문에 상속을 유용하게 사용할 수 있습니다. 이 질문에 유용할까요? 모르겠습니다. 질문은 충분히 구체적이지 않습니다.
Jens Schauder

@JensSchauder 트레이 트는 동작 측면에서 동일한 것을 제공하는 것처럼 보이며 유형 클래스보다 유연성이 떨어집니다. 나는 케이스 클래스 속성의 중복되지 않는 것을 얻고 있는데, 특성이나 추상 클래스는 일반적으로 피하는 데 도움이 될 것입니다.
virtualeyes

7

이러한 상황에서는 상속 대신 구성을 사용하는 경향이 있습니다.

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

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


3
이 나타납니다 유일한 진정한 중복 필드가 발생하지 않는 것을 여기에 해답이 될 수 있습니다
앨런 토마스
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.