Có một cách khá "chuẩn" để mã hóa các loại tổng thành ngôn ngữ hướng đối tượng.
Đây là hai ví dụ:
type Either<'a, 'b> = Left of 'a | Right of 'b
Trong C #, chúng ta có thể biểu hiện điều này như sau:
interface Either<A, B> {
C Match<C>(Func<A, C> left, Func<B, C> right);
}
class Left<A, B> : Either<A, B> {
private readonly A a;
public Left(A a) { this.a = a; }
public C Match<C>(Func<A, C> left, Func<B, C> right) {
return left(a);
}
}
class Right<A, B> : Either<A, B> {
private readonly B b;
public Right(B b) { this.b = b; }
public C Match<C>(Func<A, C> left, Func<B, C> right) {
return right(b);
}
}
Một lần nữa:
type List<'a> = Nil | Cons of 'a * List<'a>
C # một lần nữa:
interface List<A> {
B Match<B>(B nil, Func<A, List<A>, B> cons);
}
class Nil<A> : List<A> {
public Nil() {}
public B Match<B>(B nil, Func<A, List<A>, B> cons) {
return nil;
}
}
class Cons<A> : List<A> {
private readonly A head;
private readonly List<A> tail;
public Cons(A head, List<A> tail) {
this.head = head;
this.tail = tail;
}
public B Match<B>(B nil, Func<A, List<A>, B> cons) {
return cons(head, tail);
}
}
Việc mã hóa là hoàn toàn cơ học. Mã hóa này tạo ra một kết quả có hầu hết các ưu điểm và nhược điểm giống nhau của các loại dữ liệu đại số. Bạn cũng có thể nhận ra đây là một biến thể của Mẫu khách truy cập. Chúng tôi có thể thu thập các tham số để Match
cùng nhau vào một giao diện mà chúng tôi có thể gọi là Khách truy cập.
Về mặt lợi thế, điều này cung cấp cho bạn một mã hóa nguyên tắc của các loại tổng. (Đó là mã hóa Scott .) Nó cung cấp cho bạn "khớp mẫu" đầy đủ mặc dù chỉ có một "lớp" khớp. Match
về mặt nào đó, giao diện "hoàn chỉnh" cho các loại này và mọi hoạt động bổ sung mà chúng tôi muốn có thể được xác định theo nghĩa của nó. Nó trình bày một góc nhìn khác về nhiều mẫu OO như Mẫu đối tượng Null và Mẫu trạng thái như tôi đã chỉ ra trong câu trả lời của Ryathal, cũng như Mẫu khách truy cập và Mẫu tổng hợp. Kiểu Option
/ Maybe
giống như một mẫu đối tượng Null chung. Mẫu tổng hợp gần giống với mã hóa type Tree<'a> = Leaf of 'a | Children of List<Tree<'a>>
. Mẫu trạng thái về cơ bản là một bảng mã của một bảng liệt kê.
Về mặt bất lợi, như tôi đã viết, Match
phương thức này đặt ra một số ràng buộc về những lớp con nào có thể được thêm vào một cách có ý nghĩa, đặc biệt nếu chúng ta muốn duy trì Thuộc tính thay thế Liskov. Ví dụ, áp dụng mã hóa này cho loại liệt kê sẽ không cho phép bạn mở rộng bảng liệt kê một cách có ý nghĩa. Nếu bạn muốn mở rộng bảng liệt kê, bạn sẽ phải thay đổi tất cả người gọi và người triển khai ở mọi nơi giống như khi bạn đang sử dụng enum
và switch
. Điều đó nói rằng, mã hóa này có phần linh hoạt hơn so với bản gốc. Ví dụ: chúng ta có thể thêm một người Append
triển khai List
chỉ giữ hai danh sách cho chúng ta nối thêm thời gian liên tục. Điều này sẽ hoạt động giống như các danh sách được nối với nhau nhưng sẽ được trình bày theo một cách khác.
Tất nhiên, nhiều vấn đề trong số này phải làm với thực tế Match
là phần nào (về mặt khái niệm nhưng có chủ ý) gắn liền với các lớp con. Nếu chúng tôi sử dụng các phương thức không cụ thể, chúng tôi sẽ có được các thiết kế OO truyền thống hơn và chúng tôi lấy lại được khả năng mở rộng, nhưng chúng tôi mất "tính hoàn chỉnh" của giao diện và do đó chúng tôi mất khả năng xác định bất kỳ hoạt động nào trên loại này theo điều khoản của giao diện. Như đã đề cập ở nơi khác, đây là một biểu hiện của Vấn đề Biểu hiện .
Có thể cho rằng, các thiết kế như trên có thể được sử dụng một cách có hệ thống để loại bỏ hoàn toàn nhu cầu phân nhánh từng đạt được lý tưởng OO. Ví dụ, Smalltalk sử dụng mẫu này thường bao gồm cho chính Booleans. Nhưng như các cuộc thảo luận trước đây cho thấy, việc "loại bỏ phân nhánh" này là khá ảo tưởng. Chúng tôi vừa thực hiện phân nhánh theo một cách khác, và nó vẫn có nhiều thuộc tính giống nhau.