Trong một vài tuần, tôi đã suy nghĩ về mối quan hệ giữa các đối tượng - đặc biệt là các đối tượng của OOP. Ví dụ, trong C ++ , chúng ta thường thể hiện điều đó bằng cách phân lớp các con trỏ hoặc vùng chứa các con trỏ trong cấu trúc cần quyền truy cập vào đối tượng khác. Nếu một đối tượng A
cần phải có quyền truy cập vào B
, nó không phải là hiếm để tìm thấy một B *pB
trong A
.
Nhưng tôi không còn là lập trình viên C ++ nữa, tôi viết chương trình bằng các ngôn ngữ chức năng và đặc biệt hơn là trong Haskell , đây là một ngôn ngữ chức năng thuần túy. Có thể sử dụng con trỏ, tài liệu tham khảo hoặc loại công cụ đó, nhưng tôi cảm thấy lạ với điều đó, giống như cách thực hiện nó theo cách không phải Haskell.
Sau đó, tôi suy nghĩ sâu hơn một chút về tất cả những thứ liên quan và đi đến điểm:
Tại sao chúng ta thậm chí đại diện cho mối quan hệ như vậy bằng cách xếp lớp?
Tôi đọc một số người đã nghĩ về điều đó ( ở đây ). Theo quan điểm của tôi, việc thể hiện các mối quan hệ thông qua các biểu đồ rõ ràng là cách tốt hơn vì nó cho phép chúng ta tập trung vào cốt lõi của loại hình của mình và thể hiện quan hệ sau đó thông qua các tổ hợp (giống như SQL ).
Theo cốt lõi, ý tôi là khi chúng ta xác định A
, chúng ta sẽ xác định những gì A
được tạo ra , chứ không phải những gì nó phụ thuộc vào . Ví dụ, trong một trò chơi video, nếu chúng ta có một loại Character
, đó là hợp pháp để nói về Trait
, Skill
hoặc loại công cụ, nhưng là nó nếu chúng ta nói về Weapon
hay Items
? Tôi không còn chắc chắn nữa. Sau đó:
data Character = {
chSkills :: [Skill]
, chTraits :: [Traits]
, chName :: String
, chWeapon :: IORef Weapon -- or STRef, or whatever
, chItems :: IORef [Item] -- ditto
}
Nghe có vẻ thực sự sai về mặt thiết kế đối với tôi. Tôi thích một cái gì đó như:
data Character = {
chSkills :: [Skill]
, chTraits :: [Traits]
, chName :: String
}
-- link our character to a Weapon using a Graph Character Weapon
-- link our character to Items using a Graph Character [Item] or that kind of stuff
Hơn nữa, khi một ngày đến để thêm các tính năng mới, chúng ta chỉ có thể tạo các loại mới, biểu đồ và liên kết mới. Trong thiết kế đầu tiên, chúng ta sẽ phải phá vỡ Character
loại hoặc sử dụng một số loại công việc xung quanh để mở rộng nó.
Bạn nghĩ gì về ý tưởng đó? Bạn nghĩ gì là tốt nhất để giải quyết vấn đề đó trong Haskell , một ngôn ngữ chức năng thuần túy?
Character
nên giữ vũ khí hay không. Khi bạn có một bản đồ từ Characters
đến Weapons
và một nhân vật vắng mặt trên bản đồ, điều đó có nghĩa là nhân vật đó hiện không cầm vũ khí hay nhân vật đó không thể giữ vũ khí? Hơn nữa, bạn sẽ thực hiện các tra cứu không cần thiết bởi vì bạn không biết tiên nghiệm liệu Character
có thể giữ vũ khí hay không.
canHoldWeapons :: Bool
lá cờ, cho phép bạn biết ngay nếu nó có thể giữ vũ khí, và nếu nhân vật của bạn không trong biểu đồ bạn có thể nói rằng nhân vật hiện không cầm vũ khí. Hãy giveCharacterWeapon :: WeaponGraph -> Character -> Weapon -> WeaponGraph
hành động như id
thể nếu cờ đó là False
dành cho nhân vật được cung cấp hoặc sử dụng Maybe
để thể hiện sự thất bại.
-Wall
trên GHC : data Foo = Foo { foo :: Int } | Bar { bar :: String }
. Sau đó, gõ kiểm tra để gọi foo (Bar "")
hoặc bar (Foo 0)
, nhưng cả hai sẽ đưa ra ngoại lệ. Nếu bạn đang sử dụng các hàm tạo dữ liệu kiểu vị trí thì không có vấn đề gì vì trình biên dịch không tạo ra các trình truy cập cho bạn, nhưng bạn không có được sự tiện lợi của các loại bản ghi cho các cấu trúc lớn và phải sử dụng nhiều bản tóm tắt hơn thư viện như ống kính.