วิธีการตรวจสอบหลักการทดแทน Liskov ในลำดับชั้นการสืบทอด?


14

แรงบันดาลใจจากคำตอบนี้ :

หลักการทดแทน Liskov ต้องการ สิ่งนั้น

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

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

หมายเหตุ:
ฉันหวังว่าจะโพสต์ตัวอย่างโค้ดเพื่อให้คนทำงาน แต่คำถามนั้นเกี่ยวกับวิธีระบุลำดับชั้นผิดพลาด :)


มีตัวอย่างอื่น ๆ ของการละเมิด LSP ในคำตอบของคำถาม SO นี้
StuartLC

คำตอบ:


17

มันง่ายกว่าคำพูดนั้นทำให้ฟังดูแม่นยำและแม่นยำ

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

ตัวอย่างเช่น ( แต่เดิมเห็นในเว็บไซต์ของลุงบ็อบ ):

public class Square : Rectangle
{
    public Square(double width) : base(width, width)
    {
    }

    public override double Width
    {
        set
        {
            base.Width = value;
            base.Height = value;
        }
        get
        {
            return base.Width;
        }
    }

    public override double Height
    {
        set
        {
            base.Width = value;
            base.Height = value;
        }
        get
        {
            return base.Height;
        }
    }
}

ดูเหมือนยุติธรรมพอใช่ไหม ฉันได้สร้างสี่เหลี่ยมผืนผ้าชนิดผู้เชี่ยวชาญที่เรียกว่า Square ซึ่งรักษาความกว้างนั้นต้องเท่ากับความสูงตลอดเวลา สี่เหลี่ยมจตุรัสเป็นสี่เหลี่ยมดังนั้นมันจึงเหมาะกับหลักการ OO ใช่ไหม

แต่เดี๋ยวก่อนจะเกิดอะไรขึ้นถ้ามีคนเขียนวิธีนี้:

public void Enlarge(Rectangle rect, double factor)
{
    rect.Width *= factor;
    rect.Height *= factor;
}

ไม่เท่ห์ แต่ไม่มีเหตุผลที่ผู้เขียนวิธีการนี้ควรจะรู้ว่าอาจมีปัญหาที่อาจเกิดขึ้น

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


ตัวอย่างที่ดีมากและละเอียดอ่อน +1 สิ่งที่คุณสามารถทำได้คือทำให้การขยายวิธีการของระดับสี่เหลี่ยมผืนผ้าและแทนที่ในชั้นเรียนสแควร์
marco-fiset

@ marco-fiset: ฉันอยากเห็นสแควร์และสี่เหลี่ยมผืนผ้าแยกกัน, สแควร์ที่มีเพียงหนึ่งมิติ แต่แต่ละการใช้ IResizable เป็นความจริงที่ว่าหากมีวิธีการวาดพวกเขาจะคล้ายกัน แต่จากนั้นฉันอยากให้พวกเขาทั้งสองเรียนแค็ปซูลคลาส RectangleDrawer ซึ่งรวมถึงรหัสทั่วไปด้วย
pdr

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

@pdr ขอบคุณสำหรับตัวอย่าง แต่เกี่ยวกับ 4 เงื่อนไขที่ฉันกล่าวถึงในโพสต์ของฉันซึ่งส่วนหนึ่งของSquareชั้นเรียนละเมิดพวกเขา?
Songo

1
@Songo: มันเป็นข้อ จำกัด ทางประวัติศาสตร์ อธิบายได้ดีกว่าที่นี่: blackwasp.co.uk/LSP.aspx "โดยธรรมชาติแล้วคลาสย่อยรวมถึงวิธีการและคุณสมบัติทั้งหมดของซูเปอร์คลาสของพวกเขานอกจากนี้พวกเขาอาจเพิ่มสมาชิกเพิ่มเติมได้อีกด้วยข้อ จำกัด ด้านประวัติศาสตร์บอกว่าสมาชิกใหม่หรือสมาชิกที่แก้ไขไม่ควรแก้ไข สถานะของวัตถุในลักษณะที่จะไม่ได้รับอนุญาตจากคลาสพื้นฐานตัวอย่างเช่นถ้าคลาสพื้นฐานแสดงวัตถุที่มีขนาดคงที่คลาสย่อยควรไม่อนุญาตให้ปรับขนาดนี้ "
pdr
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.