2012-10-03 21 views
69

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 chữ hoa, chủ yếu là vì tôi thấy thuận tiện để có các phương thức sao chép.thừa kế lớp vỏ Scala

Tôi có hai mô hình có liên quan chặt chẽ. Các trường là giống nhau, nhiều thao tác là chung, và chúng đượ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 trường hợp đơn, với cờ phân biệt loại mô hình và tất cả các phương pháp khác nhau dựa trên loại mô hình bắt đầu bằng if. Điều này gây phiền toái và không khá an toàn.

Điều tôi muốn làm là yếu tố hành vi và trường phổ biến trong 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, kế thừa từ các trường hợp lớp học được cau mày khi ở Scala, và thậm chí bị cấm nếu lớp con là chính nó là một trường hợp lớp (không phải trường hợp của tôi).

What are the problems and pitfalls I should be aware in inheriting from a case class? Does it make sense in my case to do so?

+1

Bạn không thể kế thừa từ một lớp không phải là trường hợp, hay mở rộng một đặc điểm chung? – Eduardo

+0

Tôi không chắc chắn. Các trường được xác định trong tổ tiên. Tôi muốn nhận được các phương pháp sao chép, bình đẳng và vân vân dựa trên những lĩnh vực đó. Nếu tôi khai báo cha mẹ như là một lớp trừu tượng và trẻ em như là một lớp trường hợp, nó sẽ đưa vào tài khoản các thông số được xác định trên phụ huynh? – Andrea

+0

Tôi nghĩ rằng không, bạn phải xác định đạo cụ trong cả hai phụ huynh trừu tượng (hoặc đặc điểm) và trường hợp mục tiêu lớp. Cuối cùng, rất nhiều 'boilerplate, nhưng gõ an toàn ít nhất – virtualeyes

Trả lời

89

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

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 trở thành hạt mịn hơn , 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 
+48

đâu là "không có sự sao chép mã" mà bạn nói đến? Có, một hợp đồng được xác định giữa trường hợp lớp và cha mẹ của nó (s), nhưng bạn vẫn gõ ra đạo cụ X2 – virtualeyes

+2

@ 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 là số lượng mã nhiều hơn các thuộc tính. –

+1

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

12

lớp chữ thường là 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 bằng các giá trị bằng.

Nhưng việc thực hiện bằng với sự hiện diện của thừa kế khá phức tạp. Hãy xem xét mộ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 các ColorPoint (1,4, đỏ) nên bằng với điểm (1,4) họ là những điểm tương tự sau tất cả. Vì vậy, ColorPoint (1,4, màu xanh) cũng phải bằng Điểm (1,4), phải không? Nhưng tất nhiên ColorPoint (1,4, màu đỏ) không nên bằng ColorPoint (1,4, màu xanh), bởi vì chúng có màu sắc khác nhau. Có bạn đi, một tài sản cơ bản của mối quan hệ bình đẳng bị phá vỡ.

cập nhật

Bạn có thể sử dụng thừa kế từ những đặc điểm giải quyết rất nhiều vấn đề như mô tả trong câu trả lời khác. Một lựa chọn linh hoạt hơn thường là sử dụng các lớp kiểu. Xem What are type classes in Scala useful for? hoặc http://www.youtube.com/watch?v=sVMES4RZF-8

+0

Tôi hiểu và đồng ý với điều đó. Vì vậy, những gì bạn đề nghị nên được thực hiện khi bạn có một ứng dụng mà đề với, nói, sử dụng lao động và nhân viên. Giả sử rằng chúng chia sẻ tất cả các trường (tên, địa chỉ, vv), sự khác biệt duy nhất là trong một số phương thức - ví dụ người ta có thể muốn định nghĩa 'Employer.fire (e: Emplooyee)' nhưng không phải là cách khác. Tôi muốn tạo ra 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

+0

có một ví dụ về cách tiếp cận loại lớp với câu hỏi ở đây? tức là đối với trường hợp các lớp học – virtualeyes

+0

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

36

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

Bạn có thể đi với các phương pháp sau đây:

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

Vâng, bạn phải lặp lại trong các lĩnh vực. Nếu bạn không, nó sẽ không thể thực hiện bình đẳng chính xác among other problems.

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

Nếu việc sao chép một vài thuộc tính quan trọng đối với bạn, 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ì thừa kế:

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

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

Thành phần là hợp lệ và một chiến lược âm thanh mà bạn nên xem xét là tốt.

Và trong trường hợp bạn tự hỏi những gì một đặc điểm niêm phong có nghĩa là - nó là cái gì đó có thể được mở rộng chỉ trong cùng một tập tin. Tức là, hai lớp vỏ 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 đầy đủ:

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

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

Cung cấp cho một lỗi:

warning: match is not exhaustive! 
missing combination   Tourist 

Đó là thực sự hữu ích. Bây giờ bạn sẽ không quên để đối phó với các loại khác của Person s (người). Đây là cơ bản những gì lớp học Option trong Scala 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 chữ hoa vào các tệp riêng của chúng. Và có lẽ đi với bố cục.

+1

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

3

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

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 bạn có thể sử dụng một hệ thống phân cấp phức tạp hơn và các trận đấu nhưng hy vọng điều này sẽ cho bạn một ý tưởng. Điều quan trọng là tận dụng lợi thế của các bộ giải nén lồng nhau mà các trường hợp cung cấp