ปัญหาวงกลมวงรีสามารถแก้ไขได้โดยการย้อนกลับความสัมพันธ์หรือไม่?


13

การCircleยืดเวลาEllipseแบ่งหลักการ Liskov Substitionเนื่องจากมันจะแก้ไข postcondition: คุณสามารถตั้งค่า X และ Y อย่างอิสระเพื่อวาดวงรี แต่ X ต้องเท่ากับ Y เสมอสำหรับวงกลม

แต่นี่ไม่ใช่ปัญหาที่เกิดจากการมี Circle เป็นชนิดย่อยของ Ellipse หรือไม่? เราไม่สามารถย้อนกลับความสัมพันธ์หรือไม่

ดังนั้นวงกลม supertype - setRadiusมันมีวิธีเดียว

จากนั้นขยายวงรีวงกลมโดยการเพิ่มและsetX setYการเรียกsetRadiusใช้ Ellipse จะเป็นการตั้งค่าทั้ง X และ Y หมายถึง postcondition บน setRadius ได้รับการดูแล แต่ตอนนี้คุณสามารถตั้งค่า X และ Y ได้อย่างอิสระผ่านอินเทอร์เฟซเสริม


1
คุณดูใน Wikipedia ก่อน ( en.wikipedia.org/wiki/Circle-ellipse_problem ) หรือไม่
Doc Brown

1
ใช่ - ฉันยังเชื่อมโยงในคำถามของฉัน ...
HorusKol

6
และจุดที่แน่นอนนี้ครอบคลุมในบทความนั้นดังนั้นฉันไม่ชัดเจนว่าคุณถามอะไร
Philip Kendall

6
ผู้เขียนบางคนแนะนำให้ย้อนกลับความสัมพันธ์ระหว่างวงกลมกับวงรีเนื่องจากว่าวงรีนั้นเป็นวงที่มีความสามารถเพิ่มเติมโชคไม่ดีที่วงรีไม่สามารถตอบสนองค่าคงที่ของวงกลมได้ถ้า Circle มีรัศมีวิธี เพื่อจัดหาให้เช่นกัน "
Philip Kendall

3
สิ่งที่ฉันพบว่าเป็นคำอธิบายที่ชัดเจนเกี่ยวกับสาเหตุที่ปัญหานี้มีสถานที่ที่ไม่ดีวางที่ด้านล่างสุดของบทความวิกิพีเดีย: en.wikipedia.org/wiki/... ทั้งนี้ขึ้นอยู่กับสถานการณ์ที่มีการออกแบบหลายสะอาด แต่มันขึ้นอยู่กับสิ่งที่คุณต้องการจากทั้งสองเรียนที่จะทำไม่เป็น
Arthur Havlicek

คำตอบ:


38

แต่นี่ไม่ใช่ปัญหาที่เกิดจากการมี Circle เป็นชนิดย่อยของ Ellipse หรือไม่? เราไม่สามารถย้อนกลับความสัมพันธ์หรือไม่

ปัญหาเกี่ยวกับสิ่งนี้ (และปัญหาของสี่เหลี่ยมจัตุรัส / สี่เหลี่ยมผืนผ้า) กำลังสมมติว่ามีความสัมพันธ์ในโดเมนหนึ่ง (เรขาคณิต) เท็จในอีกอันหนึ่ง (พฤติกรรม)

วงกลมและวงรีนั้นสัมพันธ์กันหากคุณดูผ่านปริซึมของทฤษฎีเชิงเรขาคณิต แต่นั่นไม่ใช่โดเมนเดียวที่คุณสามารถดูได้

วัตถุ orientated ข้อเสนอการออกแบบที่มีลักษณะการทำงาน

ลักษณะการกำหนดของวัตถุคือพฤติกรรมที่วัตถุรับผิดชอบ และในโดเมนของพฤติกรรมวงกลมและวงรีมีพฤติกรรมที่แตกต่างกันซึ่งน่าจะดีกว่าที่จะไม่คิดว่ามันเกี่ยวข้องกันเลย ในโดเมนนี้วงรีและวงกลมไม่มีความสัมพันธ์ที่สำคัญ

บทเรียนที่นี่คือการเลือกโดเมนที่เหมาะสมที่สุดสำหรับ OOD ไม่ใช่การลองและใส่รองเท้าแตะในความสัมพันธ์เพียงเพราะมีอยู่ในโดเมนอื่น

ตัวอย่างที่พบเห็นได้ทั่วไปในความผิดพลาดนี้คือสมมติว่าวัตถุนั้นมีความสัมพันธ์กัน (หรือแม้แต่คลาสเดียวกัน) เพราะพวกมันมีข้อมูลที่คล้ายคลึงกันแม้ว่าพฤติกรรมของพวกเขาจะแตกต่างกันมาก นี่เป็นปัญหาที่พบบ่อยเมื่อคุณเริ่มสร้างวัตถุ "data first" โดยการกำหนดว่าข้อมูลจะไปที่ใด คุณสามารถจบลงด้วยคลาสที่เกี่ยวข้องผ่านข้อมูลที่มีพฤติกรรมแตกต่างไปจากเดิมอย่างสิ้นเชิง ตัวอย่างเช่นทั้งเงินเดือนและวัตถุของพนักงานอาจมีแอตทริบิวต์ "เงินเดือนขั้นต้น" แต่พนักงานไม่ใช่ประเภทของสลิปเงินเดือนและการจ่ายเงินเดือนไม่ใช่ประเภทของพนักงาน


การแยกข้อกังวลของโดเมน (แอปพลิเคชัน) กับความสามารถด้านพฤติกรรมและความรับผิดชอบของ OOD เป็นประเด็นที่สำคัญมาก ตัวอย่างเช่นในแอปพลิเคชั่นการวาดภาพคุณอาจจะสามารถแปรเปลี่ยนเป็นวงกลมเป็นสี่เหลี่ยมจัตุรัสได้ แต่สิ่งนี้ไม่ได้จำลองแบบง่าย ๆ โดยใช้คลาส / วัตถุในภาษาส่วนใหญ่ ดังนั้นโดเมนแอปพลิเคชันไม่ได้แมปกับลำดับชั้นการสืบทอดภาษาของ OOP ที่กำหนดไว้เสมอไปและเราไม่ควรพยายามบังคับให้ใช้ ในหลายกรณีองค์ประกอบจะดีกว่า
Erik Eidt

3
คำตอบนี้เป็นสิ่งที่ดีที่สุดที่ฉันเคยเห็นเกี่ยวกับปัญหาทั้งหมดและความเป็นไปได้ที่จะเกิดข้อผิดพลาดในการออกแบบอาจเกิดขึ้นได้ในกรณีทั่วไปมากขึ้น ขอบคุณ
HorusKol

1
@ErikEidt ปัญหาของพฤติกรรมการเปลี่ยนวัตถุสามารถแก้ไขได้ใน OOD ผ่านการสลายตัว ตัวอย่างเช่นหากรูปร่างที่เปลี่ยนแปลงได้เป็นวงกลมคุณไม่จำเป็นต้องเปลี่ยนชั้นเรียน แต่คลาสจะใช้วัตถุพฤติกรรมทางเรขาคณิตในปัจจุบันซึ่งคุณสามารถสลับไปใช้กับพฤติกรรมอื่นเมื่อคุณเปลี่ยนรูป คลาสอื่นนี้มีกฎของรูปทรงเรขาคณิตที่กำลังถูกจำลองและคลาสรูปร่างที่ปรับเปลี่ยนได้จะเลื่อนไปที่คลาสนี้สำหรับพฤติกรรมทางเรขาคณิต หากวัตถุเปลี่ยนไปเป็นคลาสอื่นคุณจะเปลี่ยนคลาสพฤติกรรมเป็นอย่างอื่น
Cormac Mulhall

2
@Cormac ใช่ไหม! โดยทั่วไปฉันจะเรียกว่ารูปแบบของการแต่งเพลงตามที่ฉันกล่าวถึงแม้ว่าคุณสามารถระบุรูปแบบกลยุทธ์หรือบางสิ่งได้ ในสาระสำคัญคุณมีตัวตนที่ไม่แปรเปลี่ยนและสิ่งอื่น ๆ ที่สามารถเปลี่ยนแปลงได้ สรุปโดยรวมแล้วเป็นการเน้นถึงความแตกต่างระหว่างแนวคิดของโดเมนแอปพลิเคชันและรายละเอียดของ OOP ของภาษาที่กำหนดและความต้องการในการแมประหว่างกัน (เช่นสถาปัตยกรรมการออกแบบและการเขียนโปรแกรม)
Erik Eidt

1
แต่งานสามารถเป็น paycheck

8

แวดวงเป็นกรณีพิเศษของวงรีนั่นคือทั้งสองแกนของวงรีนั้นเหมือนกัน มันเป็นความเท็จโดยพื้นฐานในโดเมนปัญหา (เรขาคณิต) ที่ระบุว่ารูปไข่อาจเป็นวงกลม การใช้โมเดลที่มีข้อบกพร่องนี้จะเป็นการละเมิดการรับประกันของวงกลมเช่น“ ทุกจุดในวงกลมนั้นมีระยะห่างจากศูนย์กลางเท่ากัน” นั่นก็จะเป็นการละเมิดหลักการแทน Liskov วงรีจะมีรัศมีเดียวได้อย่างไร (ไม่ใช่setRadius()แต่สำคัญกว่าgetRadius())

ในขณะที่การสร้างแบบจำลองของวงกลมเป็นประเภทย่อยของวงรีไม่ผิดอย่างยิ่ง แต่เป็นการแนะนำของความไม่แน่นอนที่ทำลายรูปแบบนี้ หากไม่มีsetX()และsetY()วิธีการจะไม่มีการละเมิด LSP หากจำเป็นต้องมีวัตถุที่มีขนาดต่างกันการสร้างอินสแตนซ์ใหม่เป็นทางออกที่ดีกว่า:

class Ellipse {
  final double x;
  final double y;
  ...
  Ellipse withX(double newX) {
    return new Ellipse(x: newX, y: y);
  }
}

1
โอเค - ดังนั้นถ้ามีอินเตอร์เฟซทั่วไประหว่างEllipseและCircle(เช่นgetArea) ที่จะถูกแยกเป็นประเภทShape- สามารถEllipseและCircleแยกย่อยจากShapeและตอบสนอง LSP?
HorusKol

1
@HorusKol ใช่ สองคลาสที่รับค่าอินเทอร์เฟซที่พวกเขาทั้งสองใช้จริงอย่างถูกต้องนั้นใช้ได้อย่างสมบูรณ์
Ixrec

7

คอร์แม็กมีคำตอบที่ดีมาก แต่ฉันแค่ต้องการอธิบายรายละเอียดเกี่ยวกับเหตุผลของความสับสนในตอนแรก

การสืบทอดใน OO มักสอนโดยใช้คำอุปมาอุปมัยในโลกแห่งความจริงเช่น "แอปเปิ้ลและส้มเป็นทั้งผลไม้ย่อย" น่าเสียดายที่สิ่งนี้นำไปสู่ความเชื่อที่ผิดที่ประเภทใน OO ควรถูกสร้างแบบจำลองตามลำดับชั้นของอนุกรมวิธานบางอย่างที่เป็นอิสระจากโปรแกรม

แต่ในการออกแบบซอฟต์แวร์ประเภทควรเป็นแบบจำลองตามข้อกำหนดของแอปพลิเคชัน การจำแนกประเภทในโดเมนอื่นมักไม่เกี่ยวข้อง ในแอปพลิเคชันจริงที่มีวัตถุ "Apple" และ "Orange" - กล่าวว่าระบบการจัดการสินค้าคงคลังสำหรับซุปเปอร์มาร์เก็ต - พวกเขาอาจจะไม่ได้เรียนที่แตกต่างกันและหมวดหมู่เช่น "ผลไม้" จะเป็นคุณลักษณะมากกว่าประเภทซุปเปอร์

ปัญหาวงรีวงรีคือปลาเฮอริ่งแดง ในเรขาคณิตวงกลมเป็นลักษณะเฉพาะของวงรี แต่คลาสในตัวอย่างของคุณไม่ใช่ตัวเลขทางเรขาคณิต รูปทรงเรขาคณิตที่สำคัญไม่สามารถเปลี่ยนแปลงได้ พวกมันสามารถถูกเปลี่ยนรูปได้ แต่จากนั้นสามารถเปลี่ยนเป็นวงรีได้ ดังนั้นแบบจำลองที่วงกลมสามารถเปลี่ยนรัศมี แต่ไม่เปลี่ยนเป็นจุดไข่ปลาไม่ตรงกับเรขาคณิต แบบจำลองดังกล่าวอาจสมเหตุสมผลในแอปพลิเคชันเฉพาะ (พูดเครื่องมือวาดรูป) แต่การจำแนกทางเรขาคณิตไม่เกี่ยวข้องกับวิธีที่คุณออกแบบลำดับชั้นของชั้นเรียน

ดังนั้น Circle ควรเป็น subclass ของ Ellipse หรือในทางกลับกัน มันขึ้นอยู่กับข้อกำหนดของแอพพลิเคชั่นเฉพาะซึ่งใช้วัตถุเหล่านี้โดยสิ้นเชิง แอปพลิเคชั่นการวาดอาจมีตัวเลือกที่แตกต่างกันในการรักษาวงกลมและจุดไข่ปลา:

  1. ปฏิบัติต่อแวดวงและจุดไข่ปลาเป็นรูปร่างที่แตกต่างกันด้วย UI ที่แตกต่างกัน (เช่นสองมือจับปรับขนาดบนจุดไข่ปลาหนึ่งด้ามบนวงกลม) ซึ่งหมายความว่าคุณสามารถมีวงรีซึ่งเป็นรูปวงกลม แต่ไม่ได้เป็นวงกลมจากมุมมองของแอปพลิเคชัน

  2. ปฏิบัติต่อจุดไข่ปลาทั้งหมดรวมถึงแวดวงเดียวกัน แต่มีตัวเลือก "ล็อค" x และ y เป็นค่าเดียวกัน

  3. วงรีเป็นเพียงวงกลมที่มีการใช้การแปลงสเกล

แต่ละการออกแบบที่เป็นไปได้จะนำไปสู่รูปแบบวัตถุที่แตกต่างกัน -

ในกรณีที่ 1 Circle และ Ellipses จะเป็นคลาสพี่น้อง

ในอันที่สองจะไม่มีคลาส Circle ที่แตกต่างออกไปเลย

ในอันที่ 3 จะไม่มีคลาส Ellipse ที่แตกต่างกัน ดังนั้นปัญหาวงรีวงรีที่เรียกว่าไม่ได้ป้อนรูปภาพในสิ่งเหล่านี้

ดังนั้นเพื่อตอบคำถามตามที่ตั้งไว้: วงกลมควรขยายวงรีหรือไม่? คำตอบคือ: ขึ้นอยู่กับสิ่งที่คุณต้องการจะทำกับมัน แต่อาจจะไม่


1
คำตอบที่ดีมาก!
Utsav T

6

มันเป็นความผิดพลาดตั้งแต่เริ่มต้นที่จะยืนยันว่ามี "Ellipse" และคลาส "Circle" โดยที่หนึ่งเป็น subclass ของอีกอัน คุณมีสองตัวเลือกที่เหมือนจริง: หนึ่งในนั้นก็คือต้องแยกชั้นเรียน พวกเขาอาจมี superclass ทั่วไปสำหรับสิ่งต่าง ๆ เช่นสีไม่ว่าจะเป็นวัตถุที่เติมเต็มความกว้างของเส้นสำหรับการวาดภาพ ฯลฯ

อีกอันหนึ่งจะมีคลาสหนึ่งชื่อ "Ellipse" เท่านั้น หากคุณมีคลาสนั้นมันก็ง่ายพอที่จะใช้เพื่อเป็นตัวแทนของแวดวง (อาจมีกับดักขึ้นอยู่กับรายละเอียดการใช้งานวงรีจะมีมุมและการคำนวณมุมนั้นจะต้องไม่เป็นปัญหาสำหรับวงรีรูปวงกลม) คุณสามารถมีวิธีพิเศษสำหรับวงรีวงกลมได้ แต่ "วงรีวงกลม" เหล่านี้จะยังคงเป็นวัตถุ "วงรี" เต็มรูปแบบ


อาจมีวิธี IsCircle ซึ่งจะตรวจสอบเพื่อดูว่าวัตถุเฉพาะของคลาส Ellipse ในความเป็นจริงมีทั้งสองแกนเดียวกัน คุณชี้ให้เห็นปัญหามุมด้วย แวดวงไม่สามารถ 'หมุน'

3

หลังจากติดตามคะแนน LSP แล้วคำตอบที่ 'เหมาะสม' สำหรับปัญหานี้คือ @HorusKol และ @Ixrec มาจากทั้งสองประเภทจาก Shape แต่ขึ้นอยู่กับรุ่นที่คุณใช้งานอยู่ดังนั้นคุณควรกลับไปใช้แบบนั้นเสมอ

สิ่งที่ฉันสอนคือ:

หากประเภทย่อยไม่สามารถดำเนินการพฤติกรรมเช่นเดียวกับประเภทซุปเปอร์ความสัมพันธ์ไม่ได้อยู่ในสถานที่ตั้งของ IS-A - มันควรจะมีการเปลี่ยนแปลง

  • ประเภทย่อยคือ SUPERSET ของซุปเปอร์ประเภท
  • super-type เป็น SUBSET ของ sub-type

เป็นภาษาอังกฤษ:

  • ประเภทที่ได้รับคือ SUPERSET ของประเภทฐาน
  • ประเภทฐานเป็น SUBSET ของประเภทที่ได้รับ

(ตัวอย่าง:

  • รถยนต์ที่มีท่อไอเสียแบบไม่ดียังคงเป็นรถยนต์อยู่ (อ้างอิงจากบางส่วน)
  • รถที่ไม่มีเครื่องยนต์, ล้อ, แร็คพวงมาลัย, ระบบขับเคลื่อนและเหลือเพียงเปลือกเท่านั้นไม่ใช่ 'รถยนต์' เป็นเพียงเปลือกหอย)

นั่นเป็นวิธีการจำแนกประเภท (เช่นในโลกของสัตว์) และหลักใน OO

การใช้สิ่งนี้เป็นคำจำกัดความของการสืบทอดและความหลากหลาย (ซึ่งมักถูกเขียนด้วยกัน) หากหลักการนี้พังคุณควรพยายามคิดใหม่อีกครั้งว่าคุณกำลังพยายามทำตัวแบบ

ตามที่กล่าวไว้โดย @HorusKul และ @Ixrec ในคณิตศาสตร์คุณได้กำหนดประเภทไว้อย่างชัดเจน แต่ในวิชาคณิตศาสตร์วงกลมเป็นวงรีเนื่องจากเป็น SUBSET ของวงรี แต่ใน OOP นี่ไม่ใช่วิธีการถ่ายทอดทางพันธุกรรม คลาสควรสืบทอดถ้ามันเป็น SUPERSET (ส่วนขยาย) ของคลาสที่มีอยู่ - ความหมายมันยังคงเป็นคลาสพื้นฐานในบริบททั้งหมด

จากที่ฉันคิดว่าวิธีการแก้ปัญหาควรได้รับการพูดจาเล็กน้อย

มีประเภทฐานรูปร่างแล้วปัดเศษรูปร่าง (มีวงกลมอย่างมีประสิทธิภาพ แต่ฉันใช้ชื่ออื่นที่นี่ DELIBERATELY ... )

... จากนั้นวงรี

ทางนั้น:

  • RoundedShape เป็นรูปร่าง
  • วงรีเป็นรูปทรงกลม

(สิ่งนี้ทำให้รู้สึกถึงผู้คนในภาษาเรามีแนวคิดที่ชัดเจนเกี่ยวกับ 'วงกลม' ในใจของเราอยู่แล้วและสิ่งที่เรากำลังพยายามทำที่นี่โดยการสรุป (การรวม) ทำให้เกิดแนวคิดนั้น)


แนวคิดที่ชัดเจนของเรานั้นไม่ได้นำมาใช้ในทางปฏิบัติเสมอไป

-1

จากวงรีเปอร์สเปคทีฟของ OO จะขยายวงกลมมันเชี่ยวชาญโดยการเพิ่มคุณสมบัติบางอย่าง คุณสมบัติของวงกลมที่มีอยู่ยังคงเป็นวงรีมันจะซับซ้อนและเฉพาะเจาะจงมากขึ้น ฉันไม่เห็นปัญหาใด ๆ กับพฤติกรรมในกรณีนี้เช่นเดียวกับที่คอร์แม็กทำรูปร่างไม่มีพฤติกรรม ปัญหาเดียวคือในแง่ของภาษาศาสตร์หรือคณิตศาสตร์มันไม่รู้สึกถูกต้องที่จะพูดว่า "วงรีคือวงกลม" เพราะประเด็นทั้งหมดของแบบฝึกหัดที่ไม่ได้กล่าวถึง แต่เป็นการบอกเป็นนัยถึงการจัดประเภทรูปทรงเรขาคณิต นั่นอาจเป็นเหตุผลที่ดีในการพิจารณาว่าวงกลมและวงรีเป็นเพื่อนไม่ได้เชื่อมโยงพวกเขาโดยการสืบทอดและยอมรับว่าพวกเขาเพิ่งจะมีคุณสมบัติเดียวกันบางอย่างและไม่ปล่อยให้จิตใจ OO ที่บิดเบี้ยวของคุณเข้ามาสังเกต

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.