Kế thừa lớp trường hợp Scala


88

Tôi có một ứng dụng dựa trên Squeryl. Tôi định nghĩa các mô hình của mình là các lớp trường hợp, chủ yếu là vì tôi thấy thuận tiện khi có các phương thức sao chép.

Tôi có hai mô hình có liên quan chặt chẽ với nhau. Các trường giống nhau, nhiều thao tác chung và chúng phải được lưu trữ trong cùng một bảng DB. Nhưng có một số hành vi chỉ có ý nghĩa trong một trong hai trường hợp, hoặc có ý nghĩa trong cả hai trường hợp nhưng khác nhau.

Cho đến bây giờ tôi chỉ sử dụng một lớp trường hợp duy nhất, với một cờ phân biệt loại mô hình và tất cả các phương thức khác nhau dựa trên loại mô hình đều bắt đầu bằng dấu nếu. Điều này là khó chịu và không phải là loại hoàn toàn an toàn.

Những gì tôi muốn làm là tính toán các hành vi và trường chung trong một lớp trường hợp tổ tiên và có hai mô hình thực tế kế thừa từ nó. Nhưng, theo như tôi hiểu, việc kế thừa từ các lớp trường hợp không được chấp nhận trong Scala và thậm chí bị cấm nếu bản thân lớp con là một lớp trường hợp (không phải trường hợp của tôi).

Những vấn đề và cạm bẫy tôi cần lưu ý khi kế thừa từ một lớp trường hợp là gì? Làm như vậy có hợp lý không trong trường hợp của tôi?


1
Bạn không thể kế thừa từ một lớp không phải chữ hoa chữ thường hoặc mở rộng một đặc điểm chung?
Eduardo

Tôi không chắc. Các trường được xác định trong tổ tiên. Tôi muốn nhận các phương thức sao chép, bình đẳng, v.v. dựa trên các trường đó. Nếu tôi khai báo cha là một lớp trừu tượng và con là một lớp trường hợp, nó có tính đến các tham số được xác định trên cha không?
Andrea

Tôi nghĩ là không, bạn phải xác định các đạo cụ trong cả lớp cha (hoặc đặc điểm) trừu tượng và lớp trường hợp đích. Cuối cùng, o' soạn sẵn rất nhiều, nhưng loại an toàn ít nhất
virtualeyes

Câu trả lời:


118

Cách ưa thích của tôi để tránh kế thừa lớp trường hợp mà không có mã trùng lặp có phần rõ ràng: tạo một lớp cơ sở chung (trừu tượng):

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


Nếu bạn muốn chi tiết hơn, hãy nhóm các thuộc tính thành các đặc điểm riêng lẻ:

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

83
"Không có mã trùng lặp" mà bạn nói đến là ở đâu? Vâng, một hợp đồng được xác định giữa lớp trường và phụ huynh (s) của nó, nhưng bạn vẫn đang gõ ra đạo cụ X2
virtualeyes

5
@virtualeyes Đúng, bạn vẫn phải lặp lại các thuộc tính. Tuy nhiên, bạn không phải lặp lại các phương thức, thường chiếm nhiều mã hơn các thuộc tính.
Malte Schwerhoff

1
vâng, tôi chỉ hy vọng giải quyết được việc sao chép các thuộc tính - một câu trả lời khác gợi ý về các lớp kiểu như một giải pháp khả thi; Tuy nhiên, không chắc làm thế nào, dường như hướng đến sự hòa trộn trong hành vi, như các đặc điểm, nhưng linh hoạt hơn. Chỉ cần soạn lại: lớp trường hợp, có thể sống với nó, sẽ là khá đáng kinh ngạc nếu nó đã khác, thực sự có thể hack ra swaths lớn các định nghĩa tài sản
virtualeyes

1
@virtualeyes Tôi hoàn toàn đồng ý rằng sẽ thật tuyệt nếu việc lặp lại thuộc tính có thể tránh được một cách dễ dàng. Một plugin trình biên dịch chắc chắn có thể làm được thủ thuật này, nhưng tôi sẽ không gọi đó là một cách dễ dàng.
Malte Schwerhoff

13
@virtualeyes Tôi nghĩ rằng tránh trùng lặp mã không chỉ là viết ít hơn. Đối với tôi, đó là việc không có cùng một đoạn mã trong các phần khác nhau của ứng dụng của bạn mà không có bất kỳ kết nối nào giữa chúng. Với giải pháp này, tất cả các lớp con được gắn với một hợp đồng, vì vậy nếu lớp cha thay đổi, IDE sẽ có thể giúp bạn xác định các phần của mã mà bạn cần sửa.
Daniel

40

Vì đây là một chủ đề thú vị với nhiều người, hãy để tôi làm sáng tỏ ở đây.

Bạn có thể thực hiện theo cách tiếp cận sau:

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

Có, bạn phải sao chép các trường. Nếu bạn không làm vậy, đơn giản là sẽ không thể thực hiện bình đẳng đúng giữa các vấn đề khác .

Tuy nhiên, bạn không cần phải sao chép các phương thức / hàm.

Nếu sự trùng lặp của một vài thuộc tính quan trọng đối với bạn, thì hãy sử dụng các lớp thông thường, nhưng hãy nhớ rằng chúng không phù hợp với FP.

Ngoài ra, bạn có thể sử dụng thành phần thay vì kế thừa:

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

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

Sáng tác là một chiến lược hợp lệ và đúng đắn mà bạn cũng nên xem xét.

Và trong trường hợp bạn thắc mắc đặc điểm được niêm phong nghĩa là gì - nó là thứ chỉ có thể được mở rộng trong cùng một tệp. Có nghĩa là, hai lớp trường hợp trên phải nằm trong cùng một tệp. Điều này cho phép kiểm tra trình biên dịch toàn diện:

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

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

Đưa ra một lỗi:

warning: match is not exhaustive!
missing combination            Tourist

Cái nào thực sự hữu ích. Bây giờ bạn sẽ không quên đối phó với các loại Person(người) khác. Đây thực chất là những gì Optionlớp trong Scala thực hiện.

Nếu điều đó không quan trọng với bạn, thì bạn có thể làm cho nó không bị niêm phong và ném các lớp trường hợp vào tệp riêng của chúng. Và có lẽ đi đôi với sáng tác.


1
Tôi nghĩ rằng def nameđặc điểm cần phải có val name. Trình biên dịch của tôi đã đưa ra cảnh báo mã không thể truy cập được với trình biên dịch trước đây.
BAR

13

các lớp trường hợp hoàn hảo cho các đối tượng giá trị, tức là các đối tượng không thay đổi bất kỳ thuộc tính nào và có thể được so sánh với bằng.

Nhưng việc triển khai bằng với sự có mặt của thừa kế là khá phức tạp. Hãy xem xét hai lớp:

class Point(x : Int, y : Int)

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

Vì vậy, theo định nghĩa, ColorPoint (1,4, đỏ) phải bằng với Điểm (1,4), xét cho cùng, chúng là cùng một Điểm. Vì vậy, ColorPoint (1,4, xanh lam) cũng phải bằng Điểm (1,4), phải không? Nhưng tất nhiên ColorPoint (1,4, đỏ) không được bằng ColorPoint (1,4, xanh lam), vì chúng có các màu khác nhau. Bạn hiểu rồi đấy, một thuộc tính cơ bản của quan hệ bình đẳng đã bị phá vỡ.

cập nhật

Bạn có thể sử dụng kế thừa từ các đặc điểm để giải quyết rất nhiều vấn đề như được mô tả trong một câu trả lời khác. Một giải pháp thay thế linh hoạt hơn thường là sử dụng các lớp kiểu. Xem Các lớp kiểu trong Scala hữu ích cho việc gì? hoặc http://www.youtube.com/watch?v=sVMES4RZF-8


Tôi hiểu và đồng ý với điều đó. Vì vậy, bạn đề nghị nên làm gì khi bạn có một ứng dụng liên quan đến, chẳng hạn như nhà tuyển dụng và người lao động. Giả sử rằng chúng chia sẻ tất cả các trường (tên, địa chỉ, v.v.), sự khác biệt duy nhất là ở một số phương thức - ví dụ, người ta có thể muốn xác định Employer.fire(e: Emplooyee)nhưng không phải theo cách khác. Tôi muốn tạo hai lớp khác nhau, vì chúng thực sự đại diện cho các đối tượng khác nhau, nhưng tôi cũng không thích sự lặp lại phát sinh.
Andrea

có một ví dụ về cách tiếp cận lớp kiểu với câu hỏi ở đây? tức là liên quan đến trường hợp lớp
virtualeyes

@virtualeyes Một người có thể có các kiểu hoàn toàn độc lập cho các loại Thực thể khác nhau và cung cấp các Lớp kiểu để cung cấp hành vi. Các Lớp Kiểu này có thể sử dụng kế thừa hữu ích nhiều nhất có thể, vì chúng không bị ràng buộc bởi hợp đồng ngữ nghĩa của các lớp trường hợp. Nó sẽ hữu ích trong câu hỏi này? Không biết, câu hỏi không đủ cụ thể để nói.
Jens Schauder

@JensSchauder có vẻ như các đặc điểm cung cấp điều tương tự về hành vi, chỉ kém linh hoạt hơn các lớp kiểu; Tôi đang nhận ra tính năng không trùng lặp của các thuộc tính lớp trường hợp, một cái gì đó mà các đặc điểm hoặc các lớp trừu tượng thông thường sẽ giúp người ta tránh.
Virtualeyes 3/10/12

7

Trong những tình huống này, tôi có xu hướng sử dụng thành phần thay vì kế thừa, tức là

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

Rõ ràng là bạn có thể sử dụng một hệ thống phân cấp phức tạp hơn và phù hợp nhưng hy vọng điều này mang lại cho bạn một ý tưởng. Chìa khóa là tận dụng các trình trích xuất lồng nhau mà các lớp trường hợp cung cấp


3
Xuất hiện này là câu trả lời duy nhất ở đây mà thực sự không có các trường trùng lặp
Alan Thomas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.