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 Horse
và một Unicorn
loạ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 Circle
là 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
Ellipse
và, 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, Unicorn
kiểu của bạn không cần phải là một kiểu con của Horse
tấ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à toUnicorn
cần phải là một nghịch đảo đúng của toHorse
:
toUnicorn (toHorse x) = Just x
Maybe
Loạ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 Unicorn
hoặ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
Horse
loại;
- Các
Horse
nhu 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
Horse
loạ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 Horse
có phải là một con kỳ lân hay không;
- Các khách hàng của
Horse
loạ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 Horse
là 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 Horse
s, 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
Horse
loại;
- Có
Unicorn
mộ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
Horse
có 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
Horse
ví dụ mô tả một con kỳ lân nhưng không phải là một Unicorn
thể 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 Horse
xem đó có phải là một Unicorn
trường hợp không đồng nghĩa với việc hỏi Horse
liệu đó có phải là một con kỳ lân không (cho dù đó là một Horse
mô 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 Horse
mô tả một con kỳ lân, Unicorn
lớ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 Horse
s thành Unicorn
s. Đây có thể là một phương thức của Horse
loạ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 Unicorn
hoạ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à Horse
loại cần để tiếp xúc với khách hàng của mình).