ดูเหมือนว่าโดเมนความหมายของคุณจะมีความสัมพันธ์กับ IS-A แต่คุณค่อนข้างระวังในการใช้ชนิดย่อย / การสืบทอดเพื่อสร้างแบบจำลองนี้โดยเฉพาะอย่างยิ่งเนื่องจากการสะท้อนชนิดรันไทม์ ฉันคิดว่าอย่างไรก็ตามคุณกลัวสิ่งที่ผิด - การพิมพ์ย่อยนั้นมาพร้อมกับอันตรายจริง ๆ แต่ความจริงที่ว่าคุณกำลังค้นหาวัตถุที่รันไทม์ไม่ใช่ปัญหา คุณจะเห็นสิ่งที่ฉันหมายถึง
การเขียนโปรแกรมเชิงวัตถุนั้นพึ่งพาความคิดของความสัมพันธ์ IS-A ค่อนข้างหนักมันมีเนื้อหาที่หนักแน่นเกินกว่าที่จะนำไปสู่แนวคิดที่มีชื่อเสียงสองประการ:
แต่ฉันคิดว่ามีวิธีการเขียนโปรแกรมที่ใช้งานได้มากกว่าเพื่อดูความสัมพันธ์ IS-A ที่อาจไม่มีปัญหาเหล่านี้ ก่อนอื่นเราต้องการสร้างแบบจำลองม้าและยูนิคอร์นในโปรแกรมของเราดังนั้นเราจะมี a Horse
และa Unicorn
type ค่าของประเภทเหล่านี้คืออะไร? ฉันจะบอกว่า:
- ค่าของประเภทเหล่านี้เป็นตัวแทนหรือคำอธิบายของม้าและยูนิคอร์น (ตามลำดับ);
- พวกเขาเป็นตัวแทนschematizedหรือคำอธิบาย - พวกเขาไม่ได้ฟรีฟอร์มพวกเขาสร้างขึ้นตามกฎที่เข้มงวดมาก
นั่นอาจฟังดูชัดเจน แต่ฉันคิดว่าหนึ่งในวิธีที่ผู้คนสามารถแก้ไขปัญหาเช่นปัญหาวงรีวงรีก็คือการไม่คำนึงถึงประเด็นเหล่านั้นอย่างรอบคอบเพียงพอ วงกลมทุกวงเป็นวงรี แต่นั่นไม่ได้หมายความว่าคำอธิบาย schematized ทุกคำของวงกลมจะเป็นคำอธิบาย schematized ของวงรีโดยอัตโนมัติตามสคีมาที่แตกต่างกัน ในคำอื่น ๆ เพียงเพราะวงกลมเป็นวงรีไม่ได้หมายความว่าCircle
เป็นEllipse
เพื่อที่จะพูด แต่มันหมายความว่า:
- มีฟังก์ชั่นทั้งหมดที่แปลง
Circle
คำอธิบายวงกลม (schematized วงกลม) ให้เป็นEllipse
(คำอธิบายประเภทอื่น) ที่อธิบายถึงวงกลมเดียวกัน
- มีความเป็นฟังก์ชั่นบางส่วนที่ใช้เวลาและถ้าอธิบายวงกลมผลตอบแทนที่สอดคล้องกัน
Ellipse
Circle
ดังนั้นในแง่การเขียนโปรแกรมเชิงฟังก์ชันUnicorn
ประเภทของคุณไม่จำเป็นต้องเป็นประเภทย่อยHorse
เลยคุณเพียงแค่ต้องการการดำเนินการเช่นนี้:
-- 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
และtoUnicorn
ต้องเป็นสิ่งที่ตรงกันข้ามกับtoHorse
:
toUnicorn (toHorse x) = Just x
Maybe
ประเภทของ Haskell เป็นสิ่งที่ภาษาอื่นเรียกว่า "ตัวเลือก" ประเภท ตัวอย่างเช่นOptional<Unicorn>
ประเภทJava 8 เป็นได้Unicorn
หรือไม่มีอะไร โปรดทราบว่าทางเลือกสองทางของคุณ ได้แก่ การยกเว้นหรือส่งคืน "ค่าเริ่มต้นหรือค่าเวทย์มนตร์" - คล้ายกับประเภทตัวเลือกมาก
ดังนั้นสิ่งที่ฉันทำที่นี่คือสร้างแนวคิด IS-A ขึ้นมาใหม่ในแง่ของประเภทและฟังก์ชั่นโดยไม่ต้องใช้ชนิดย่อยหรือการสืบทอด สิ่งที่ฉันจะเอาไปจากนี้คือ:
- แบบจำลองของคุณต้องมี
Horse
ชนิด
Horse
ประเภทความต้องการในการเข้ารหัสข้อมูลเพียงพอที่จะตรวจสอบอย่างไม่น่าสงสัยว่าค่าใด ๆ อธิบายยูนิคอร์น;
- การดำเนินการบางอย่างของ
Horse
ประเภทต้องเปิดเผยข้อมูลนั้นเพื่อให้ลูกค้าประเภทสามารถสังเกตได้ว่าการให้Horse
เป็นยูนิคอร์น;
- ลูกค้า
Horse
ประเภทนี้จะต้องใช้การดำเนินการหลังเหล่านี้ที่รันไทม์เพื่อแยกแยะระหว่างยูนิคอร์นและม้า
ดังนั้นนี่คือ "ถามทุกคนHorse
ว่ามันเป็นยูนิคอร์น" หรือไม่ คุณระวังรูปแบบนั้น แต่ฉันคิดผิดอย่างนั้น ถ้าฉันให้รายชื่อของHorse
คุณสิ่งที่ประเภทรับประกันคือสิ่งที่รายการในรายการอธิบายคือม้า - ดังนั้นคุณย่อมต้องทำบางสิ่งบางอย่างที่รันไทม์เพื่อบอกว่าพวกมันคือยูนิคอร์น ฉันคิดว่าคุณไม่จำเป็นต้องดำเนินการใด ๆ ที่จะทำเพื่อคุณ
ในการเขียนโปรแกรมเชิงวัตถุวิธีที่คุ้นเคยในการทำสิ่งนี้มีดังต่อไปนี้:
- มี
Horse
ประเภท;
- มี
Unicorn
เป็นชนิดย่อยของHorse
;
- ใช้การสะท้อนกลับชนิดรันไทม์เป็นการดำเนินการที่ลูกค้าเข้าถึงได้ซึ่งระบุว่าได้รับมา
Horse
หรือUnicorn
ไม่
นี่เป็นจุดอ่อนที่ยิ่งใหญ่เมื่อคุณดูจากมุม "สิ่งของกับคำอธิบาย" ที่ฉันนำเสนอไว้ด้านบน:
- ถ้าคุณมี
Horse
อินสแตนซ์ที่อธิบายยูนิคอร์น แต่ไม่ใช่Unicorn
อินสแตนซ์ล่ะ?
กลับไปที่จุดเริ่มต้นนี่คือสิ่งที่ฉันคิดว่าเป็นส่วนที่น่ากลัวจริงๆเกี่ยวกับการใช้ subtyping และ downcasts สำหรับการสร้างแบบจำลองความสัมพันธ์ IS-A นี้ - ไม่ใช่ความจริงที่ว่าคุณต้องทำการตรวจสอบรันไทม์ เหยียดหยามพิมพ์บิตถามHorse
ไม่ว่าจะเป็นUnicorn
ตัวอย่างที่ไม่ตรงกันกับการถามHorse
ไม่ว่าจะเป็นยูนิคอร์น (ไม่ว่าจะเป็นHorse
-description ม้าที่ยังเป็นยูนิคอร์น) ไม่เว้นเสียแต่ว่าโปรแกรมของคุณจะมีความยาวมาก ๆ เพื่อห่อหุ้มโค้ดที่สร้างขึ้นHorses
เพื่อให้ทุกครั้งที่ไคลเอ็นต์พยายามสร้างสิ่งHorse
ที่อธิบายถึงยูนิคอร์นUnicorn
คลาสจะถูกสร้างอินสแตนซ์ จากประสบการณ์ของฉันโปรแกรมเมอร์ไม่ค่อยทำสิ่งนี้อย่างระมัดระวัง
ดังนั้นฉันจะไปกับวิธีการที่มีการดำเนินการที่ชัดเจนและไม่ดาวน์สตรีมที่แปลงHorse
s เป็นUnicorn
s นี่อาจเป็นวิธีการของHorse
ประเภท:
interface Horse {
// ...
Optional<Unicorn> toUnicorn();
}
... หรืออาจเป็นวัตถุภายนอก ("วัตถุแยกต่างหากของคุณบนม้าที่บอกคุณว่าม้านั้นเป็นยูนิคอร์นหรือไม่"):
class HorseToUnicornCoercion {
Optional<Unicorn> convert(Horse horse) {
// ...
}
}
ทางเลือกระหว่างเหล่านี้เป็นเรื่องของวิธีการที่โปรแกรมของคุณจะจัดในทั้งสองกรณีคุณมีเทียบเท่าของฉันHorse -> Maybe Unicorn
การดำเนินงานจากข้างต้นคุณก็บรรจุภัณฑ์ในรูปแบบที่แตกต่างกัน (ที่เป็นที่ยอมรับจะมีผลกระทบต่อเนื่องกับสิ่งที่ดำเนินงานHorse
ความต้องการประเภท เพื่อเปิดเผยให้กับลูกค้า)