CẬP NHẬT 25/02/2016:
Mặc dù câu trả lời tôi viết bên dưới vẫn đủ, nhưng cũng đáng để tham khảo một câu trả lời liên quan khác cho câu hỏi này liên quan đến đối tượng đồng hành của lớp trường hợp. Cụ thể, làm thế nào để người ta tái tạo chính xác đối tượng đồng hành ngầm định được tạo ra bởi trình biên dịch xảy ra khi người ta chỉ định nghĩa chính lớp trường hợp. Đối với tôi, nó hóa ra là phản trực quan.
Tóm tắt:
Bạn có thể thay đổi giá trị của một tham số lớp trường hợp trước khi nó được lưu trữ trong lớp trường hợp khá đơn giản trong khi nó vẫn còn là một ADT hợp lệ (ated) (Kiểu dữ liệu trừu tượng). Mặc dù giải pháp tương đối đơn giản, nhưng việc khám phá các chi tiết lại khó hơn một chút.
Chi tiết:
Nếu bạn muốn đảm bảo chỉ có thể khởi tạo các trường hợp hợp lệ của lớp trường hợp của mình, đây là một giả định thiết yếu đằng sau ADT (Kiểu dữ liệu trừu tượng), có một số điều bạn phải làm.
Ví dụ, một copy
phương thức do trình biên dịch tạo ra được cung cấp theo mặc định trên một lớp trường hợp. Vì vậy, ngay cả khi bạn đã rất cẩn thận để đảm bảo chỉ các phiên bản được tạo thông qua apply
phương thức của đối tượng đồng hành rõ ràng , điều này đảm bảo rằng chúng chỉ có thể chứa các giá trị chữ hoa, đoạn mã sau sẽ tạo ra một trường hợp lớp có giá trị chữ thường:
val a1 = A("Hi There")
val a2 = a1.copy(s = "gotcha")
Ngoài ra, các lớp trường hợp thực hiện java.io.Serializable
. Điều này có nghĩa là chiến lược cẩn thận của bạn để chỉ có các trường hợp chữ hoa có thể bị lật đổ bằng một trình chỉnh sửa văn bản đơn giản và deserialization.
Vì vậy, đối với tất cả các cách khác nhau mà lớp trường hợp của bạn có thể được sử dụng (nhân từ và / hoặc ác ý), đây là các hành động bạn phải thực hiện:
- Đối với đối tượng đồng hành rõ ràng của bạn:
- Tạo nó bằng cách sử dụng chính xác tên giống như lớp trường hợp của bạn
- Điều này có quyền truy cập vào các phần riêng tư của lớp trường hợp
- Tạo ra một
apply
phương thức có cùng chữ ký với hàm tạo chính cho lớp trường hợp của bạn
- Điều này sẽ biên dịch thành công khi bước 2.1 hoàn thành
- Cung cấp một triển khai lấy một phiên bản của lớp case bằng cách sử dụng
new
toán tử và cung cấp một triển khai trống{}
- Điều này bây giờ sẽ khởi tạo loại trường hợp nghiêm ngặt theo các điều khoản của bạn
- Việc triển khai trống
{}
phải được cung cấp vì lớp trường hợp được khai báo abstract
(xem bước 2.1)
- Đối với loại trường hợp của bạn:
- Khai báo nó
abstract
- Ngăn trình biên dịch Scala tạo một
apply
phương thức trong đối tượng đồng hành là nguyên nhân gây ra lỗi biên dịch "phương thức được xác định hai lần ..." (bước 1.2 ở trên)
- Đánh dấu hàm tạo chính là
private[A]
- Hàm tạo chính hiện chỉ có sẵn cho chính lớp trường hợp và đối tượng đồng hành của nó (cái mà chúng tôi đã xác định ở trên trong bước 1.1)
- Tạo một
readResolve
phương pháp
- Cung cấp triển khai bằng phương pháp áp dụng (bước 1.2 ở trên)
- Tạo một
copy
phương pháp
- Xác định nó có chữ ký chính xác như hàm tạo chính của lớp case
- Đối với mỗi tham số, hãy thêm một giá trị mặc định bằng cách sử dụng cùng một tên tham số (ví dụ:
s: String = s
:)
- Cung cấp triển khai bằng phương pháp áp dụng (bước 1.2 bên dưới)
Đây là mã của bạn đã được sửa đổi với các hành động trên:
object A {
def apply(s: String, i: Int): A =
new A(s.toUpperCase, i) {}
}
abstract case class A private[A] (s: String, i: Int) {
private def readResolve(): Object =
A.apply(s, i)
def copy(s: String = s, i: Int = i): A =
A.apply(s, i)
}
Và đây là mã của bạn sau khi thực hiện yêu cầu (được đề xuất trong câu trả lời @ollekullberg) và cũng xác định vị trí lý tưởng để đặt bất kỳ loại bộ nhớ đệm nào:
object A {
def apply(s: String, i: Int): A = {
require(s.forall(_.isUpper), s"Bad String: $s")
new A(s, i) {}
}
}
abstract case class A private[A] (s: String, i: Int) {
private def readResolve(): Object =
A.apply(s, i)
def copy(s: String = s, i: Int = i): A =
A.apply(s, i)
}
Và phiên bản này an toàn / mạnh mẽ hơn nếu mã này sẽ được sử dụng thông qua Java interop (ẩn lớp trường hợp như một sự triển khai và tạo ra một lớp cuối cùng ngăn chặn các dẫn xuất):
object A {
private[A] abstract case class AImpl private[A] (s: String, i: Int)
def apply(s: String, i: Int): A = {
require(s.forall(_.isUpper), s"Bad String: $s")
new A(s, i)
}
}
final class A private[A] (s: String, i: Int) extends A.AImpl(s, i) {
private def readResolve(): Object =
A.apply(s, i)
def copy(s: String = s, i: Int = i): A =
A.apply(s, i)
}
Trong khi điều này trực tiếp trả lời câu hỏi của bạn, thậm chí còn có nhiều cách hơn để mở rộng con đường này xung quanh các lớp trường hợp ngoài bộ nhớ đệm cá thể. Đối với nhu cầu dự án của riêng tôi, tôi đã tạo ra một giải pháp mở rộng hơn nữa mà tôi đã ghi lại trên CodeReview (một trang web chị em của StackOverflow). Nếu bạn cuối cùng đã xem qua, sử dụng hoặc tận dụng giải pháp của tôi, vui lòng cân nhắc để lại cho tôi phản hồi, đề xuất hoặc câu hỏi và trong lý do, tôi sẽ cố gắng hết sức để trả lời trong vòng một ngày.