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