รูปแบบของรัฐละเมิดหลักการทดแทน Liskov หรือไม่?


17

ภาพนี้นำมาจากการใช้การออกแบบและรูปแบบที่ใช้โดเมนขับเคลื่อน: พร้อมตัวอย่างใน C # และ. NET

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือแผนภาพคลาสสำหรับรูปแบบสถานะที่ a SalesOrderสามารถมีสถานะต่างกันในช่วงเวลาของชีวิต อนุญาตให้ช่วงการเปลี่ยนภาพบางอย่างระหว่างรัฐที่แตกต่างกัน

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

ตอนนี้ไม่ได้เป็นการละเมิดหลักการทดแทนของ Liskov เนื่องจาก sublcass ไม่ควรเปลี่ยนพฤติกรรมของผู้ปกครองหรือไม่ การเปลี่ยนคลาสนามธรรมเป็นอินเตอร์เฟสแก้ไขสิ่งนี้หรือไม่?
จะแก้ไขได้อย่างไร?


เปลี่ยน OrderState ให้เป็นคลาสที่เป็นรูปธรรมซึ่งส่งข้อยกเว้น "NotSupported" ในทุกวิธีโดยค่าเริ่มต้นหรือไม่
James

ฉันไม่ได้อ่านหนังสือเล่มนี้ แต่ดูเหมือนว่าแปลกสำหรับฉันว่า OrderState มี Cancel () จากนั้นก็มีสถานะ Cancelled ซึ่งเป็นประเภทย่อยที่แตกต่างกัน
ร่าเริง

@Eurhoric ทำไมมันแปลก? การโทรcancel()เปลี่ยนสถานะเป็นยกเลิก
Songo

อย่างไร? คุณจะเรียกยกเลิกใน OrderState ได้อย่างไรเมื่ออินสแตนซ์ของสถานะใน SalesOrder ควรมีการเปลี่ยนแปลง ฉันคิดว่าแบบจำลองทั้งหมดผิด ฉันต้องการที่จะเห็นการใช้งานเต็มรูปแบบ
ร่าเริง

คำตอบ:


3

การใช้งานเฉพาะนี้ใช่ หากคุณสร้างคลาสที่เป็นรูปธรรมมากกว่าที่จะใช้นามธรรมคุณจะได้รับจากสิ่งนี้

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

ลองนึกภาพว่าทุกส่วนของระบบที่ดูแลเกี่ยวกับรัฐอยู่ในบริการที่แตกต่างกันกระบวนการที่แตกต่างกันในเครื่องที่แตกต่างกันผู้จัดการสถานะกลางที่มีรายละเอียดสถานะของแต่ละสถานที่เหล่านั้นอย่างมีประสิทธิภาพคอขวดระบบกระจายทั้งหมดนี้และฉันคิดว่า ของการละเมิด SRP รวมถึงการออกแบบที่ไม่ดีโดยทั่วไป

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

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

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


โดยทั่วไปปัญหาใหญ่คือการวางแนววัตถุนั้นดีสำหรับการเพิ่มคลาสใหม่ แต่ทำให้ยากที่จะเพิ่มวิธีการใหม่ และในกรณีของรัฐดังที่คุณกล่าวว่าเป็นไปไม่ได้ที่คุณจะต้องขยายรหัสด้วยสถานะใหม่บ่อยครั้งมาก
hugomg

3
@ จิมมี่ฮอฟฟาขออภัยสิ่งที่คุณหมายถึงอย่างแน่นอนโดย "ถ้าคุณทำให้รัฐที่เป็นรูปธรรมในชั้นเรียนมากกว่าผู้ใช้นามธรรมแล้วคุณจะได้รับไปจากนี้" ?
Songo

@Jimmy: ฉันพยายามดำเนินการของรัฐโดยไม่ต้องรูปแบบของรัฐโดยมีแต่ละองค์ประกอบเพียงรู้เกี่ยวกับสถานะของตัวเอง ในที่สุดการเปลี่ยนแปลงครั้งใหญ่ทำให้เกิดความซับซ้อนมากขึ้นในการนำไปใช้และบำรุงรักษา ที่กล่าวว่าฉันคิดว่าการใช้เครื่องรัฐ (นึกคิดคนอื่นห้องสมุดเพื่อบังคับให้ถือว่าเป็นกล่องดำ) มากกว่ารูปแบบการออกแบบรัฐ (ดังนั้นรัฐเป็นเพียงองค์ประกอบภายใน enum มากกว่าคลาสเต็มและการเปลี่ยนเป็นเพียงใบ้ edge) ให้ประโยชน์หลายอย่างของรูปแบบของรัฐในขณะที่เป็นปฏิปักษ์ต่อความพยายามของผู้พัฒนาในทางที่ผิด
Brian

8

sublcass ไม่ควรเปลี่ยนพฤติกรรมของผู้ปกครองหรือไม่

นั่นเป็นการตีความที่ผิดพลาดทั่วไปของ LSP คลาสย่อยสามารถเปลี่ยนพฤติกรรมของพาเรนต์ได้ตราบใดที่มันยังคงเป็นจริงกับประเภทพาเรนต์

มีคำอธิบายที่ดีเกี่ยวกับWikipediaซึ่งแนะนำสิ่งต่าง ๆ ที่จะเป็นการละเมิด LSP:

... มีเงื่อนไขพฤติกรรมหลายอย่างที่ประเภทย่อยจะต้องปฏิบัติตาม สิ่งเหล่านี้มีรายละเอียดในคำศัพท์ที่คล้ายกับการออกแบบโดยวิธีการทำสัญญาซึ่งนำไปสู่ข้อ จำกัด บางประการเกี่ยวกับวิธีที่สัญญาสามารถโต้ตอบกับมรดกได้:

  • เงื่อนไขเบื้องต้นไม่สามารถเสริมความแข็งแกร่งในประเภทย่อย
  • Postconditions ไม่สามารถลดลงได้ในประเภทย่อย
  • ค่าคงที่ของซูเปอร์ไทป์จะต้องเก็บรักษาไว้ในประเภทย่อย
  • ข้อ จำกัด ประวัติ ("กฎประวัติศาสตร์") วัตถุนั้นได้รับการยกย่องว่าสามารถปรับเปลี่ยนได้ด้วยวิธีการของพวกเขาเท่านั้น (encapsulation) เนื่องจากชนิดย่อยอาจแนะนำวิธีการที่ไม่ปรากฏใน supertype การแนะนำวิธีการเหล่านี้อาจอนุญาตให้มีการเปลี่ยนแปลงสถานะในชนิดย่อยที่ไม่อนุญาตใน supertype ข้อ จำกัด ประวัติห้ามสิ่งนี้ มันเป็นองค์ประกอบใหม่ที่ได้รับการแนะนำโดย Liskov และ Wing การละเมิดข้อ จำกัด นี้สามารถเป็นตัวอย่างได้โดยการกำหนด MutablePoint เป็นประเภทย่อยของ ImmutablePoint นี่เป็นการละเมิดข้อ จำกัด ด้านประวัติเพราะในประวัติศาสตร์ของจุดที่ไม่เปลี่ยนรูปแบบรัฐจะเหมือนเดิมเสมอหลังจากการสร้างดังนั้นจึงไม่สามารถรวมประวัติของ MutablePoint โดยทั่วไปได้ อย่างไรก็ตามฟิลด์ที่ถูกเพิ่มลงในชนิดย่อยอาจถูกปรับเปลี่ยนได้อย่างปลอดภัยเนื่องจากไม่สามารถสังเกตเห็นได้ด้วยวิธีการของ supertype

โดยส่วนตัวแล้วฉันคิดว่ามันง่ายกว่าที่จะจำสิ่งนี้: ถ้าฉันดูพารามิเตอร์ในวิธีที่มีประเภท A คนที่จะผ่านประเภทย่อย B จะทำให้ฉันประหลาดใจบ้างไหม? หากพวกเขาจะมีการละเมิด LSP

การขว้างข้อยกเว้นแปลกใจหรือไม่? ไม่ได้จริงๆ เป็นสิ่งที่สามารถเกิดขึ้นได้ตลอดเวลาไม่ว่าฉันจะเรียกใช้วิธีการจัดส่งบน OrderState หรือ Granted หรือ Shipped ดังนั้นฉันต้องคำนึงถึงมันและไม่ใช่การละเมิด LSP จริงๆ

ที่กล่าวว่าฉันคิดว่ามีวิธีที่ดีกว่าในการจัดการสถานการณ์นี้ ถ้าฉันเขียนสิ่งนี้ใน C # ฉันจะใช้ส่วนต่อประสานและตรวจสอบการใช้งานส่วนต่อประสานก่อนที่จะเรียกใช้เมธอด ตัวอย่างเช่นถ้า OrderState ปัจจุบันไม่ได้ใช้ IShippable อย่าเรียกใช้วิธีการจัดส่ง

แต่ฉันก็จะไม่ใช้รูปแบบของรัฐสำหรับสถานการณ์เฉพาะนี้ รูปแบบสถานะมีความเหมาะสมกับสถานะของแอปพลิเคชันมากกว่าสถานะของวัตถุโดเมนเช่นนี้

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


ฟังดูราวกับว่าคุณกำลังสับสน LSP กับหลักการของความประหลาดใจ / ความประหลาดใจน้อยที่สุดฉันเชื่อว่า LSP มีขอบเขตที่ชัดเจนยิ่งขึ้นซึ่งนี่เป็นการละเมิดพวกเขาแม้ว่าคุณจะใช้ POLA เชิงอัตวิสัยมากขึ้นซึ่งเป็นไปตามความคาดหวัง ; นั่นคือแต่ละคนคาดหวังสิ่งที่แตกต่างจากคนต่อไป LSP ตั้งอยู่บนพื้นฐานของการรับประกันตามสัญญาและการกำหนดที่ดีที่กำหนดโดยผู้ให้บริการไม่ได้ประเมินความคาดหวังจากผู้บริโภค
Jimmy Hoffa

@JimmyHoffa: ถูกต้องและนั่นเป็นเหตุผลที่ฉันสะกดความหมายที่แน่นอนก่อนที่ฉันจะระบุวิธีที่ง่ายกว่าในการจดจำ LSP อย่างไรก็ตามหากมีคำถามในใจว่า "เซอร์ไพรส์" หมายถึงอะไรฉันจะกลับไปตรวจสอบกฎที่แน่นอนของ LSP (คุณสามารถเรียกคืนพวกเขาตามความประสงค์ได้จริงหรือไม่) การยกเว้นการแทนที่ฟังก์ชัน (หรือในทางกลับกัน) เป็นสิ่งที่ยากเพราะมันไม่ได้ละเมิดข้อใดข้อหนึ่งข้างต้นซึ่งอาจเป็นเงื่อนงำที่ว่ามันควรได้รับการจัดการในลักษณะที่แตกต่างกันโดยสิ้นเชิง
pdr

1
ข้อยกเว้นคือเงื่อนไขที่คาดไว้ที่กำหนดไว้ในสัญญาในใจของฉันคิดถึง NetworkSocket มันอาจมีข้อยกเว้นที่คาดไว้ในการส่งหากยังไม่เปิดถ้าการใช้งานของคุณไม่ส่งข้อยกเว้นสำหรับซ็อกเก็ตปิดที่คุณละเมิด LSP หากสัญญาระบุสิ่งที่ตรงกันข้ามและประเภทย่อยของคุณโยนข้อยกเว้นแสดงว่าคุณกำลังละเมิด LSP นั่นเป็นวิธีที่ฉันจำแนกข้อยกเว้นใน LSP มากหรือน้อย สำหรับการจดจำกฎ ฉันอาจจะผิดเกี่ยวกับเรื่องนี้และรู้สึกฟรีที่จะบอกฉันมาก แต่ความเข้าใจของฉัน LSP VS POLA ถูกกำหนดไว้ในประโยคสุดท้ายของความคิดเห็นของฉันข้างต้น
จิมมี่ฮอฟฟา

1
@ JimmyHoffa: ฉันไม่เคยคิดว่า POLA และ LSP จะเชื่อมต่อจากระยะไกล (ฉันคิดว่า POLA ในแง่ UI) แต่ตอนนี้คุณได้ชี้ให้เห็นแล้วพวกเขาเป็นเช่นนั้น ฉันไม่แน่ใจว่าพวกเขากำลังขัดแย้งกันหรืออย่างใดอย่างหนึ่งเป็นส่วนตัวมากกว่าอีก LSP ไม่ใช่คำอธิบายเบื้องต้นของ POLA ในแง่วิศวกรรมซอฟต์แวร์ใช่หรือไม่ ในทำนองเดียวกับที่ฉันผิดหวังเมื่อ Ctrl-C ไม่ใช่การคัดลอก (POLA) ฉันก็รู้สึกหงุดหงิดเมื่อ ImmutablePoint ไม่สามารถเปลี่ยนแปลงได้เพราะจริงๆแล้วมันเป็น MutablePoint subclass (LSP)
pdr

1
ฉันจะถือว่าCircleWithFixedCenterButMutableRadiusเป็นการละเมิด LSP ที่เป็นไปได้หากสัญญาของผู้บริโภคImmutablePointระบุว่าหากX.equals(Y)ผู้บริโภคสามารถทดแทน X สำหรับ Y ได้อย่างอิสระและในทางกลับกันและต้องงดการใช้ชั้นเรียนในลักษณะที่การทดแทนดังกล่าวจะทำให้เกิดปัญหา ประเภทอาจกำหนดได้equalsอย่างถูกต้องตามกฎหมายซึ่งอินสแตนซ์ทั้งหมดจะเปรียบเทียบกันอย่างชัดเจน แต่พฤติกรรมดังกล่าวอาจ จำกัด ประโยชน์ของมัน
supercat

2

(นี่เขียนจากมุมมองของ C # ดังนั้นจึงไม่มีข้อยกเว้นที่ตรวจสอบแล้ว)

ตามบทความ Wikipedia เกี่ยวกับ LSPหนึ่งในเงื่อนไขของ LSP คือ:

ไม่ควรมีข้อยกเว้นใหม่โดยวิธีการของชนิดย่อยยกเว้นในกรณีที่ข้อยกเว้นเหล่านั้นเป็นชนิดย่อยของข้อยกเว้นที่ถูกโยนโดยวิธีการของประเภทย่อย

คุณควรเข้าใจเรื่องนี้อย่างไร? อะไรคือ“ ข้อยกเว้นที่ถูกโยนโดยวิธีการของ supertype” เมื่อวิธีของ supertype นั้นเป็นนามธรรม? ฉันคิดว่าสิ่งเหล่านั้นเป็นข้อยกเว้นที่บันทึกไว้เป็นข้อยกเว้นที่สามารถโยนโดยวิธีการของ supertype

สิ่งนี้หมายความว่าหากOrderState.Ship()มีการบันทึกไว้เป็นสิ่งที่ชอบ“ โยนInvalidOperationExceptionถ้าการดำเนินการนี้ไม่ได้รับการสนับสนุนโดยสถานะปัจจุบัน” จากนั้นฉันคิดว่าการออกแบบนี้ไม่ทำลาย LSP ในทางกลับกันหากวิธีการพิมพ์ supertype ไม่ได้จัดทำเอกสารด้วยวิธีนี้ LSP จะถูกละเมิด

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


จริงๆแล้วนี่คือจุดที่ทำให้ฉันคิด หากคลาสย่อยโยนข้อยกเว้นที่ไม่ได้กำหนดไว้ในพาเรนต์จะต้องเป็นการละเมิด LSP
Songo

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