Chà, có vẻ như miền ngữ nghĩa của bạn có mối quan hệ IS-A, nhưng bạn có một chút cảnh giác khi sử dụng các kiểu con / kế thừa để mô hình hóa đặc biệt này vì phản xạ kiểu thời gian chạy. Tuy nhiên, tôi nghĩ rằng bạn sợ điều sai trái của việc phân nhóm thực sự đi kèm với những nguy hiểm, nhưng thực tế là bạn đang truy vấn một đối tượng trong thời gian chạy không phải là vấn đề. Bạn sẽ thấy những gì tôi muốn nói.
Lập trình hướng đối tượng đã dựa khá nhiều vào khái niệm mối quan hệ IS-A, nó được cho là dựa quá nhiều vào nó, dẫn đến hai khái niệm quan trọng nổi tiếng:
Nhưng tôi nghĩ có một cách khác, dựa trên lập trình chức năng hơn để xem xét các mối quan hệ IS-A mà có lẽ không có những khó khăn này. Đầu tiên, chúng tôi muốn mô hình ngựa và kỳ lân trong chương trình của chúng tôi, vì vậy chúng tôi sẽ có một Horsevà một Unicornloại. Các giá trị của các loại này là gì? Vâng, tôi sẽ nói điều này:
- Các giá trị của các loại này là đại diện hoặc mô tả về ngựa và kỳ lân (tương ứng);
- Chúng là các biểu diễn hoặc mô tả được lược đồ hóa Chúng không phải là dạng tự do, chúng được xây dựng theo các quy tắc rất nghiêm ngặt.
Điều đó nghe có vẻ hiển nhiên, nhưng tôi nghĩ một trong những cách mọi người gặp phải các vấn đề như vấn đề hình elip hình tròn là bằng cách không quan tâm đến những điểm đó một cách cẩn thận. Mỗi vòng tròn là một hình elip, nhưng điều đó không có nghĩa là mọi mô tả được lược đồ hóa của một vòng tròn sẽ tự động là một mô tả được lược đồ hóa của một hình elip theo một lược đồ khác nhau. Nói cách khác, chỉ vì hình tròn là hình elip không có nghĩa là a Circlelà một Ellipse, có thể nói như vậy. Nhưng nó có nghĩa là:
- Có một hàm tổng số chuyển đổi bất kỳ
Circle(mô tả vòng tròn được lược đồ) thành một Ellipse(loại mô tả khác nhau) mô tả cùng một vòng tròn;
- Có một hàm một phần lấy một
Ellipsevà, nếu mô tả một vòng tròn, trả về tương ứng Circle.
Vì vậy, trong các thuật ngữ lập trình chức năng, Unicornkiểu của bạn không cần phải là một kiểu con của Horsetất cả, bạn chỉ cần các thao tác như sau:
-- Convert any unicorn-description of into a horse-description that
-- describes the same unicorns.
toHorse :: Unicorn -> Horse
-- If the horse described by the given horse-description is a unicorn,
-- then return a unicorn-description of that unicorn, otherwise return
-- nothing.
toUnicorn :: Horse -> Maybe Unicorn
Và toUnicorncần phải là một nghịch đảo đúng của toHorse:
toUnicorn (toHorse x) = Just x
MaybeLoại của Haskell là loại mà các ngôn ngữ khác gọi là loại "tùy chọn". Ví dụ, loại Java 8 Optional<Unicorn>là một Unicornhoặc không có gì. Lưu ý rằng hai trong số các lựa chọn thay thế của bạn, ném ra một ngoại lệ hoặc trả về một "giá trị ma thuật hoặc mặc định" rất giống với các loại tùy chọn.
Vì vậy, về cơ bản những gì tôi đã làm ở đây là xây dựng lại khái niệm mối quan hệ IS-A về các loại và chức năng, mà không sử dụng các kiểu con hoặc kế thừa. Những gì tôi sẽ lấy đi từ đây là:
- Mô hình của bạn cần phải có một
Horseloại;
- Các
Horsenhu cầu loại để mã hóa đầy đủ thông tin để xác định một cách rõ ràng cho dù bất kỳ giá trị mô tả một con kỳ lân;
- Một số hoạt động của
Horseloại cần phải phơi bày thông tin đó để khách hàng của loại có thể quan sát xem một cái đã cho Horsecó phải là một con kỳ lân hay không;
- Các khách hàng của
Horseloại sẽ phải sử dụng các hoạt động sau này trong thời gian chạy để phân biệt giữa kỳ lân và ngựa.
Vì vậy, đây về cơ bản Horselà một mô hình "hỏi mọi người xem nó có phải là một con kỳ lân" không. Bạn cảnh giác với mô hình đó, nhưng tôi nghĩ sai như vậy. Nếu tôi đưa cho bạn một danh sách Horses, tất cả những gì đảm bảo loại đó là những thứ mà các mục trong danh sách mô tả là ngựa, vì vậy, chắc chắn bạn sẽ cần phải làm gì đó trong thời gian chạy để cho biết chúng là kỳ lân. Vì vậy, không có vấn đề gì, tôi nghĩ rằng bạn cần phải thực hiện các hoạt động sẽ làm điều đó cho bạn.
Trong lập trình hướng đối tượng, cách làm quen thuộc này là như sau:
- Có một
Horseloại;
- Có
Unicornmột kiểu con của Horse;
- Sử dụng sự phản chiếu kiểu thời gian chạy như là hoạt động có thể truy cập của máy khách để phân biệt xem một cái đã cho
Horsecó phải là một Unicorn.
Điều này có một điểm yếu lớn, khi bạn nhìn nó từ góc độ "điều so với mô tả" mà tôi đã trình bày ở trên:
- Điều gì xảy ra nếu bạn có một
Horseví dụ mô tả một con kỳ lân nhưng không phải là một Unicornthể hiện?
Quay trở lại từ đầu, đây là điều tôi nghĩ là phần thực sự đáng sợ khi sử dụng phân nhóm và truyền phát để mô hình hóa mối quan hệ IS-A này không phải là việc bạn phải thực hiện kiểm tra thời gian chạy. Lạm dụng kiểu chữ một chút, hỏi Horsexem đó có phải là một Unicorntrường hợp không đồng nghĩa với việc hỏi Horseliệu đó có phải là một con kỳ lân không (cho dù đó là một Horsemô tả của một con ngựa cũng là một con kỳ lân). Không, trừ khi chương trình của bạn đã đi rất lâu để đóng gói mã xây dựng Horsesđể mỗi khi khách hàng cố gắng xây dựng một Horsemô tả một con kỳ lân, Unicornlớp sẽ được khởi tạo. Theo kinh nghiệm của tôi, hiếm khi các lập trình viên làm việc này một cách cẩn thận.
Vì vậy, tôi sẽ đi theo cách tiếp cận trong đó có một hoạt động rõ ràng, không bị hạ thấp chuyển đổi Horses thành Unicorns. Đây có thể là một phương thức của Horseloại:
interface Horse {
// ...
Optional<Unicorn> toUnicorn();
}
... Hoặc nó có thể là một vật thể bên ngoài ("vật thể riêng biệt của bạn trên một con ngựa cho bạn biết con ngựa đó có phải là một con kỳ lân hay không"):
class HorseToUnicornCoercion {
Optional<Unicorn> convert(Horse horse) {
// ...
}
}
Sự lựa chọn giữa các vấn đề này là cách thức tổ chức chương trình của bạn trong cả hai trường hợp, bạn có Horse -> Maybe Unicornhoạt động tương đương với hoạt động của tôi từ trên xuống, bạn chỉ đóng gói nó theo những cách khác nhau (điều đó sẽ thừa nhận có tác động gợn lên những hoạt động mà Horseloại cần để tiếp xúc với khách hàng của mình).