กรณีพิเศษที่มีทางเลือกล้มเหลวละเมิดหลักการทดแทน Liskov หรือไม่?


20

สมมติว่าฉันมีอินเทอร์เฟซFooInterfaceที่มีลายเซ็นต่อไปนี้:

interface FooInterface {
    public function doSomething(SomethingInterface something);
}

และคลาสรูปธรรมConcreteFooที่ใช้ส่วนต่อประสานนั้น:

class ConcreteFoo implements FooInterface {

    public function doSomething(SomethingInterface something) {
    }

}

ฉันต้องการConcreteFoo::doSomething()ทำสิ่งที่ไม่ซ้ำกันหากผ่านSomethingInterfaceวัตถุชนิดพิเศษ(สมมติว่ามันถูกเรียกว่าSpecialSomething)

มันเป็นการละเมิด LSP อย่างแน่นอนถ้าฉันเพิ่มเงื่อนไขของวิธีการหรือสร้างข้อยกเว้นใหม่ แต่มันจะเป็นการละเมิด LSP หรือไม่ถ้าฉันใส่SpecialSomethingวัตถุพิเศษในขณะที่ให้ทางเลือกสำหรับSomethingInterfaceวัตถุทั่วไป? สิ่งที่ต้องการ:

class ConcreteFoo implements FooInterface {

    public function doSomething(SomethingInterface something) {
        if (something instanceof SpecialSomething) {
            // Do SpecialSomething magic
        }
        else {
            // Do generic SomethingInterface magic
        }
    }

}

คำตอบ:


19

มันอาจจะมีการละเมิด LSP ขึ้นอยู่กับสิ่งอื่นที่อยู่ในสัญญาสำหรับdoSomethingวิธีการ แต่มันก็เป็นกลิ่นรหัสแน่นอนแม้ว่ามันจะไม่ละเมิด LSP

ตัวอย่างเช่นหากส่วนหนึ่งของสัญญาdoSomethingคือว่ามันจะเรียกsomething.commitUpdates()อย่างน้อยหนึ่งครั้งก่อนที่จะกลับมาและสำหรับกรณีพิเศษมันเรียกcommitSpecialUpdates()แทนนั่นคือการละเมิดของ LSP แม้ว่าSpecialSomething's commitSpecialUpdates()วิธีการได้รับการออกแบบอย่างมีสติที่จะทำทุกสิ่งเดียวกับcommitUpdates()ว่าเป็นเพียง preemptively แฮ็รอบละเมิด LSP และเป็นสิ่งที่จัดเรียงของ hackery หนึ่งจะไม่ต้องทำอย่างใดอย่างหนึ่งตาม LSP อย่างต่อเนื่อง ไม่ว่าสิ่งนี้จะใช้กับกรณีของคุณเป็นสิ่งที่คุณจะต้องคิดออกโดยการตรวจสอบสัญญาของคุณสำหรับวิธีการที่ (ไม่ว่าจะชัดเจนหรือโดยนัย)

เหตุผลนี้เป็นกลิ่นรหัสเนื่องจากการตรวจสอบชนิดที่เป็นรูปธรรมของข้อโต้แย้งของคุณพลาดจุดกำหนดอินเทอร์เฟซ / นามธรรมประเภทนั้นในสถานที่แรกและเนื่องจากในหลักการคุณไม่สามารถรับประกันได้ว่าวิธีการใช้งานได้อีกต่อไป ถ้ามีคนเขียนคลาสย่อยของSpecialSomethingด้วยสมมติฐานที่commitUpdates()จะได้รับการเรียก) ก่อนอื่นให้พยายามทำให้การอัปเดตพิเศษเหล่านี้ทำงานภายในที่มีอยู่SomethingInterface; นั่นเป็นผลลัพธ์ที่ดีที่สุด หากคุณแน่ใจจริงๆว่าคุณไม่สามารถทำเช่นนั้นได้คุณต้องอัปเดตอินเทอร์เฟซ หากคุณไม่ได้ควบคุมส่วนต่อประสานคุณอาจต้องเขียนส่วนต่อประสานที่ทำในสิ่งที่คุณต้องการ หากคุณไม่สามารถหาอินเทอร์เฟซที่เหมาะกับพวกเขาได้ทั้งหมดคุณอาจจะต้องทำให้เสียอินเตอร์เฟสทั้งหมดและมีหลายวิธีในการใช้รูปแบบที่แตกต่างกันหรืออาจจะเป็น refactor ที่ใหญ่กว่าก็ได้ เราต้องรู้เพิ่มเติมเกี่ยวกับเวทมนตร์ที่คุณได้แสดงความคิดเห็นเพื่อบอกว่าสิ่งใดเหมาะสม


ขอบคุณ! สิ่งนี้ช่วยได้ สมมุติฐานของวัตถุประสงค์ของdoSomething()วิธีนี้คือการแปลงแบบเป็นSpecialSomething: ถ้ามันได้รับSpecialSomethingมันก็แค่คืนวัตถุที่ไม่ได้แก้ไขในขณะที่ถ้ามันได้รับSomethingInterfaceวัตถุทั่วไปมันจะเรียกใช้อัลกอริทึมในการแปลงเป็นSpecialSomethingวัตถุ เนื่องจากเงื่อนไขและ postconditions ยังคงเหมือนเดิมฉันไม่เชื่อว่าสัญญาถูกละเมิด
Evan

1
@Evan โอ้ว้าว ... นั่นเป็นกรณีที่น่าสนใจ ที่จริงอาจไม่สมบูรณ์ สิ่งเดียวที่ฉันคิดได้ก็คือถ้าคุณส่งคืนวัตถุที่มีอยู่แทนที่จะสร้างวัตถุใหม่อาจมีบางคนขึ้นอยู่กับวิธีนี้ที่ส่งคืนวัตถุใหม่เอี่ยม ... แต่ไม่ว่าสิ่งนั้นจะทำลายคนได้หรือไม่ ภาษา. มันเป็นไปได้สำหรับคนที่จะเรียกy = doSomething(x)แล้วx.setFoo(3)และจากนั้นพบว่าy.getFoo()ผลตอบแทนที่ 3 หรือไม่?
Ixrec

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

1

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

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


1

ไม่การใช้ประโยชน์จากความจริงที่ว่าอาร์กิวเมนต์ที่กำหนดไม่เพียง แต่ให้อินเทอร์เฟซ A แต่ยัง A2 ไม่ละเมิด LSP

เพียงตรวจสอบให้แน่ใจว่าเส้นทางพิเศษนั้นไม่มีเงื่อนไขล่วงหน้าที่แข็งแกร่งกว่า (นอกเหนือจากที่ทดสอบในการตัดสินใจที่จะรับมัน) และเงื่อนไขหลังที่อ่อนแอกว่า

แม่แบบ C ++ มักจะทำเช่นนั้นเพื่อให้มีประสิทธิภาพที่ดีขึ้นเช่นโดยต้องการInputIterators แต่ให้การรับประกันเพิ่มเติมหากถูกเรียกด้วยRandomAccessIterators

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

การใช้ประโยชน์จากกรณีพิเศษมักจะขัดแย้งกับ DRY (อย่าทำซ้ำตัวเอง) เนื่องจากคุณอาจต้องทำซ้ำรหัสและต่อต้าน KISS (Keep it Simple) เนื่องจากมีความซับซ้อนมากขึ้น


0

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

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


0

หลักการทดแทน Liskov เป็นเรื่องเกี่ยวกับชนิดย่อยที่ทำหน้าที่ตามสัญญาของ supertype ของพวกเขา ดังนั้นตามที่ Ixrec เขียนมีข้อมูลไม่เพียงพอที่จะตอบว่าเป็นการละเมิด LSP หรือไม่

สิ่งที่ถูกละเมิดที่นี่แม้ว่าจะเป็นหลักการแบบเปิด หากคุณมีความต้องการใหม่ - มายากล Do SpecialSomething - และคุณมีการปรับเปลี่ยนรหัสที่มีอยู่แล้วคุณกำลังละเมิดแน่นอน OCP

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