นี่เป็นการละเมิดหลักการทดแทน Liskov หรือไม่?


132

สมมติว่าเรามีรายการของเอนทิตีงานและProjectTaskประเภทย่อย สามารถปิดงานได้ตลอดเวลายกเว้นงานProjectTasksที่ไม่สามารถปิดได้เมื่อพวกเขามีสถานะเป็นเริ่มต้น UI ควรตรวจสอบให้แน่ใจว่าไม่มีตัวเลือกในการปิดการเริ่มต้นทำงานProjectTaskแต่มีการป้องกันบางอย่างในโดเมน:

public class Task
{
     public Status Status { get; set; }

     public virtual void Close()
     {
         Status = Status.Closed;
     }
}

public class ProjectTask : Task
{
     public override void Close()
     {
          if (Status == Status.Started) 
              throw new Exception("Cannot close a started Project Task");

          base.Close();
     }
}

ตอนนี้เมื่อเรียกClose()ใช้งานมีโอกาสที่การโทรจะล้มเหลวหากเป็นProjectTaskสถานะเริ่มต้นเมื่อไม่เป็นถ้าเป็นภารกิจพื้นฐาน แต่นี่คือข้อกำหนดทางธุรกิจ มันควรจะล้มเหลว สิ่งนี้ถือได้ว่าเป็นการละเมิดหลักการทดแทน Liskovหรือไม่?


14
สมบูรณ์แบบสำหรับตัวอย่าง T ของการละเมิดการทดแทน liskov อย่าใช้การสืบทอดที่นี่และคุณจะสบายดี
จิมมี่ฮอฟฟา

8
คุณอาจต้องการเปลี่ยนเป็น: public Status Status { get; private set; }; มิฉะนั้นClose()วิธีนี้สามารถแก้ไขได้
งาน

5
อาจเป็นเพียงตัวอย่างนี้ แต่ฉันไม่เห็นประโยชน์ที่มีนัยสำคัญในการปฏิบัติตาม LSP สำหรับฉันแล้วโซลูชันในคำถามนี้มีความชัดเจนเข้าใจง่ายขึ้นและง่ายต่อการดูแลรักษามากกว่าหนึ่งวิธีที่สอดคล้องกับ LSP
Ben Lee

2
@BenLee การดูแลรักษาไม่ใช่เรื่องง่าย มันดูเป็นแบบนั้นเพราะคุณเห็นสิ่งนี้แยกจากกัน เมื่อระบบมีขนาดใหญ่ตรวจสอบให้แน่ใจว่าประเภทย่อยTaskไม่แนะนำความไม่ลงรอยกันที่แปลกประหลาดในรหัส polymorphic ที่รู้เพียงเรื่องTaskใหญ่เท่านั้น LSP ไม่ได้ตั้งใจ แต่ถูกนำมาใช้อย่างแม่นยำเพื่อช่วยในการบำรุงรักษาในระบบขนาดใหญ่
Andres F.

8
@BenLee ลองนึกภาพคุณมีกระบวนการที่TaskCloser closesAllTasks(tasks)เห็นได้ชัดว่ากระบวนการนี้ไม่ได้พยายามยกเว้น Task.Close()หลังจากทั้งหมดมันไม่ได้เป็นส่วนหนึ่งของสัญญาที่ชัดเจนของ ตอนนี้คุณแนะนำProjectTaskและทันใดนั้นคุณก็TaskCloserเริ่มโยนข้อยกเว้น (อาจจัดการ) นี่เป็นเรื่องใหญ่!
Andres F.

คำตอบ:


173

ใช่เป็นการละเมิด LSP หลักการทดแทน Liskov ต้องการสิ่งนั้น

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

ตัวอย่างของคุณแบ่งข้อกำหนดแรกโดยเสริมสร้างเงื่อนไขก่อนเรียกClose()วิธีการ

คุณสามารถแก้ไขได้โดยนำสภาพที่แข็งแกร่งมาไว้ที่ระดับบนสุดของลำดับชั้นการสืบทอด:

public class Task {
    public Status Status { get; set; }
    public virtual bool CanClose() {
        return true;
    }
    public virtual void Close() {
        Status = Status.Closed;
    }
}

โดยกำหนดว่าการเรียกร้องของClose()จะใช้ได้เฉพาะในรัฐเมื่อCanClose()ผลตอบแทนtrueให้คุณสภาพก่อนนำไปใช้Taskเช่นเดียวกับProjectTaskการแก้ไขการละเมิด LSP:

public class ProjectTask : Task {
    public override bool CanClose() {
        return Status != Status.Started;
    }
    public override void Close() {
        if (Status == Status.Started) 
            throw new Exception("Cannot close a started Project Task");
        base.Close();
    }
}

17
ฉันไม่ชอบการทำเช็คซ้ำ ฉันต้องการยกเว้นการขว้างปาเข้าไปใน Task.Close และลบ virtual จาก Close
สุข

4
@Euphoric นั่นคือความจริงการมีระดับสูงสุดCloseทำการตรวจสอบและการเพิ่มการป้องกันDoCloseจะเป็นทางเลือกที่ถูกต้อง อย่างไรก็ตามฉันต้องการอยู่ใกล้กับตัวอย่างของ OP ให้มากที่สุด การปรับปรุงเมื่อเป็นคำถามแยกต่างหาก
dasblinkenlight

5
@Eparhoric: แต่ตอนนี้ไม่มีวิธีตอบคำถาม "งานนี้จะถูกปิดหรือไม่" โดยไม่ต้องพยายามปิด สิ่งนี้บังคับให้ใช้ข้อยกเว้นสำหรับการควบคุมการไหลโดยไม่จำเป็น อย่างไรก็ตามฉันจะยอมรับว่าสิ่งนี้สามารถนำไปไกลเกินไป ไกลเกินไปวิธีการแก้ปัญหาประเภทนี้สามารถจบลงด้วยความสับสนวุ่นวาย โดยไม่คำนึงถึงคำถามของ OP ทำให้ฉันเป็นมากกว่าเกี่ยวกับหลักการดังนั้นคำตอบหอคอยงาช้างมีความเหมาะสมมาก +1
Brian

30
@Brian The CanClose ยังคงอยู่ที่นั่น มันยังสามารถถูกเรียกเพื่อตรวจสอบว่าสามารถปิดงานได้หรือไม่ การเช็คอินใน Close ควรเรียกสิ่งนี้เช่นกัน
ร่าเริง

5
@Euphoric: อ่าฉันเข้าใจผิด คุณพูดถูกที่ทำให้โซลูชั่นสะอาดขึ้นมาก
Brian

82

ใช่. สิ่งนี้ละเมิด LSP

ข้อเสนอแนะของฉันคือการเพิ่มCanCloseเมธอด / คุณสมบัติเข้ากับภารกิจพื้นฐานดังนั้นงานใด ๆ สามารถบอกได้ว่างานในสถานะนี้สามารถปิดได้หรือไม่ นอกจากนี้ยังสามารถให้เหตุผลว่าทำไม Closeและลบเสมือนจาก

ตามความคิดเห็นของฉัน:

public class Task {
    public Status Status { get; private set; }

    public virtual bool CanClose(out String reason) {
        reason = null;
        return true;
    }
    public void Close() {
        String reason;
        if (!CanClose(out reason))
            throw new Exception(reason);

        Status = Status.Closed;
    }
}

public ProjectTask : Task {
    public override bool CanClose(out String reason) {
        if (Status != Status.Started)
        {
            reason = "Cannot close a started Project Task";
            return false;
        }
        return base.CanClose(out reason);
    }
}

3
ขอบคุณสำหรับสิ่งนี้คุณเอาตัวอย่างของ dasblinkenlight ไปอีกขั้นหนึ่งแล้ว แต่ฉันก็ชอบเหตุผลที่เขาอธิบาย ขอโทษฉันไม่สามารถตอบได้ 2 คำตอบ!
Paul T Davies

ฉันสนใจที่จะรู้ว่าทำไมลายเซ็นเป็นสาธารณะเสมือนบูล CanClose (ออกจากเหตุผลสตริง) - โดยการใช้ออกคุณเป็นเพียงพิสูจน์ในอนาคต? หรือมีบางสิ่งบางอย่างลึกซึ้งยิ่งขึ้นที่ฉันหายไป?
Reacher Gilt

3
@ReacherGilt ฉันคิดว่าคุณควรตรวจสอบสิ่งที่ / อ้างอิงทำและอ่านรหัสของฉันอีกครั้ง คุณกำลังสับสน เพียงแค่ "ถ้างานไม่สามารถปิดฉันต้องการรู้ว่าทำไม"
Euphoric

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

1
และตกลงเพื่อเสริมสร้างเงื่อนไขเบื้องต้นสำหรับคุณสมบัติ CanClose หรือไม่ คือการเพิ่มเงื่อนไขหรือไม่
John V

24

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

แต่ถ้าคุณแก้ไขการTaskระบุในลายเซ็นของมันว่ามันอาจทำให้เกิดข้อยกเว้นเมื่อปิดแล้วคุณจะไม่ได้ละเมิดหลักการ


ฉันใช้ c # ซึ่งฉันไม่คิดว่ามีความเป็นไปได้นี้ แต่ฉันรู้ว่า Java ใช้
Paul T Davies

2
@PaulTDavies คุณสามารถตกแต่งวิธีการกับสิ่งที่เป็นข้อยกเว้นมันจะพ่น, msdn.microsoft.com/en-us/library/5ast78ax.aspx คุณสังเกตเห็นสิ่งนี้เมื่อคุณโฮเวอร์เหนือเมธอดจากไลบรารีคลาสพื้นฐานคุณจะได้รับรายการข้อยกเว้น มันไม่ได้บังคับใช้ แต่มันทำให้ผู้โทรทราบถึงอย่างไรก็ตาม
Despertar

18

การละเมิด LSP ต้องการสามฝ่าย Type T, Subtype S และโปรแกรม P ที่ใช้ T แต่ให้อินสแตนซ์ของ S

คำถามของคุณระบุ T (Task) และ S (ProjectTask) แต่ไม่ใช่ P ดังนั้นคำถามของคุณไม่สมบูรณ์และคำตอบนั้นผ่านการรับรอง: หากมี P ที่ไม่คาดหวังข้อยกเว้นสำหรับ P นั้นคุณมี LSP การละเมิด หาก P ทุกคนคาดว่าจะมีข้อยกเว้นแสดงว่าไม่มีการละเมิด LSP

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

  • ความรับผิดชอบ 1: แสดงภารกิจ
  • ความรับผิดชอบที่ 2: ใช้นโยบายที่เปลี่ยนแปลงสถานะของงาน

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


2
ความรับผิดชอบขึ้นอยู่กับโดเมนและ (ในตัวอย่างนี้) ความซับซ้อนของสถานะงานและตัวเปลี่ยน ในกรณีนี้ไม่มีข้อบ่งชี้ของสิ่งนั้นดังนั้นจึงไม่มีปัญหากับ SRP สำหรับการละเมิด LSP ฉันเชื่อว่าเราทุกคนสันนิษฐานว่าผู้โทรไม่คาดหวังข้อยกเว้นและแอปพลิเคชันควรแสดงข้อความที่สมเหตุสมผลแทนที่จะเข้าสู่สถานการณ์ที่ผิดพลาด
ร่าเริง

บ๊อบอันกา 'ตอบกลับ? "เราไม่คู่ควร! เราไม่คู่ควร!" อย่างไรก็ตาม ... ถ้า P ทุกคนคาดว่าจะมีข้อยกเว้นจะไม่มีการละเมิด LSP แต่ถ้าเรากำหนดอินสแตนซ์ T ไม่สามารถโยนOpenTaskException(คำใบ้คำใบ้) และP ทุกคนคาดว่าจะมีข้อยกเว้นดังนั้นสิ่งใดที่พูดเกี่ยวกับโค้ดต่ออินเทอร์เฟซไม่ใช่การนำไปใช้? ฉันกำลังพูดถึงอะไร ฉันไม่รู้ ฉันแค่พูดว่าฉันกำลังแสดงความคิดเห็นในคำตอบของบ๊อบ Unca
Radarbob

3
คุณถูกต้องว่าการพิสูจน์การละเมิด LSP ต้องใช้วัตถุสามรายการ อย่างไรก็ตามการละเมิด LSP มีอยู่หากมีโปรแกรมใด ๆ P ที่ถูกต้องในกรณีที่ไม่มีของ S แต่ล้มเหลวด้วยการเพิ่มของเอส
วินไคลน์

16

สิ่งนี้อาจจะใช่หรือไม่ใช่เป็นการละเมิด LSP

อย่างจริงจัง. ได้ยินฉัน

หากคุณปฏิบัติตาม LSP วัตถุประเภทProjectTaskต้องทำงานเป็นวัตถุประเภทTaskที่คาดว่าจะทำงาน

ปัญหาเกี่ยวกับรหัสของคุณคือคุณยังไม่ได้บันทึกว่าวัตถุประเภทTaskใดที่คาดว่าจะทำงาน คุณเขียนโค้ดแล้ว แต่ไม่มีสัญญาใด ๆ Task.Closeฉันจะเพิ่มสัญญาสำหรับ ขึ้นอยู่กับสัญญาที่ฉันเพิ่มรหัสสำหรับProjectTask.Closeทำหรือไม่ปฏิบัติตาม LSP

รับสัญญาต่อไปนี้สำหรับงานปิดรหัสสำหรับProjectTask.Close ไม่เป็นไปตาม LSP:

     // Behaviour: Moves the task to the closed state
     // and does not throw any Exception.
     // Default behaviour: Moves the task to the closed state
     // and does not throw any Exception.
     public virtual void Close()
     {
         Status = Status.Closed;
     }

ได้รับสัญญาต่อไปนี้สำหรับ Task.Close รหัสสำหรับการProjectTask.Close ไม่ปฏิบัติตาม LSP นี้:

     // Behaviour: Moves the task to the closed status if possible.
     // If this is not possible, this method throws an Exception
     // and leaves the status unchanged.
     // Default behaviour: Moves the task to the closed state
     // and does not throw any Exception.
     public virtual void Close()
     {
         Status = Status.Closed;
     }

วิธีการที่อาจถูกเขียนทับควรจัดทำเป็นเอกสารด้วยสองวิธี:

  • เอกสาร "พฤติกรรม" ที่ลูกค้าสามารถรู้ได้ว่าวัตถุใดเป็นผู้รับTaskแต่ไม่ทราบว่าคลาสเป็นอินสแตนท์โดยตรงของอะไร นอกจากนี้ยังบอกนักออกแบบของคลาสย่อยที่การแทนที่มีความสมเหตุสมผลและไม่เหมาะสม

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

ตอนนี้ความสัมพันธ์ต่อไปนี้ควรถือ:

  • ถ้า S เป็นประเภทย่อยของ T พฤติกรรมที่เป็นเอกสารของ S ควรปรับแต่งพฤติกรรมที่บันทึกไว้ของ T
  • ถ้า S เป็นประเภทย่อยของ (หรือเท่ากับ) T พฤติกรรมของรหัสของ S ควรปรับแต่งพฤติกรรมที่บันทึกไว้ของ T
  • ถ้า S เป็นประเภทย่อยของ (หรือเท่ากับ) T พฤติกรรมเริ่มต้นของ S ควรปรับแต่งพฤติกรรมที่บันทึกไว้ของ T
  • พฤติกรรมที่แท้จริงของรหัสสำหรับคลาสควรปรับแต่งพฤติกรรมเริ่มต้นของเอกสาร

@ user61852 เพิ่มจุดที่คุณสามารถระบุในลายเซ็นของวิธีการที่สามารถยกข้อยกเว้นและเพียงทำสิ่งนี้ (สิ่งที่ไม่มีรหัสผลกระทบที่เกิดขึ้นจริง) คุณจะไม่ทำลาย LSP อีกต่อไป
Paul T Davies

@PaulTDavies คุณพูดถูก แต่ในภาษาส่วนใหญ่ลายเซ็นไม่ใช่วิธีที่ดีในการประกาศว่ารูทีนอาจทำให้เกิดข้อยกเว้น ตัวอย่างเช่นใน OP (ใน C # ฉันคิดว่า) การใช้งานที่สองของการCloseโยน ดังนั้นลายเซ็นจึงประกาศว่าอาจมีข้อยกเว้นเกิดขึ้น - ไม่ได้บอกว่าจะไม่มีข้อยกเว้น Java ทำงานได้ดีขึ้นในเรื่องนี้ อย่างไรก็ตามหากคุณประกาศว่าวิธีการหนึ่งอาจประกาศข้อยกเว้นคุณควรบันทึกเหตุการณ์ที่อาจเกิดขึ้น (หรือจะ) ดังนั้นฉันยืนยันว่าเพื่อให้แน่ใจว่า LSP ถูกละเมิดหรือไม่เราต้องการเอกสารนอกเหนือจากลายเซ็น
Theodore Norvell

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

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

คุณถูกต้องว่าควรมีการบันทึกรายการข้อยกเว้นไว้ที่ใดที่หนึ่ง ฉันคิดว่าที่ที่ดีที่สุดคือรหัส มีคำถามที่เกี่ยวข้องที่นี่: stackoverflow.com/questions/16700130/…แต่คุณสามารถทำสิ่งนี้ได้โดยไม่ต้องใส่คำอธิบายประกอบ ฯลฯ ... เช่นกันเพียงแค่เขียนบางอย่างเช่นif (false) throw new Exception("cannot start")คลาสเบส คอมไพเลอร์จะลบมันและยังมีรหัสที่มีสิ่งที่จำเป็น Btw เรายังคงมีการละเมิด LSP กับวิธีการแก้ปัญหาเหล่านี้เพราะสิ่งที่จำเป็นจะยังคงมีความเข้มแข็ง ...
inf3rno

6

มันไม่ได้เป็นการละเมิดหลักการทดแทน Liskov

หลักการชดเชย Liskov พูดว่า:

Let Q (x)เป็นทรัพย์สินสามารถพิสูจน์ได้เกี่ยวกับวัตถุxประเภทT ให้Sเป็นชนิดย่อยของT ประเภทSละเมิดหลักการทดแทน Liskov หากวัตถุyของประเภทSมีอยู่เช่นนั้นq (y)ไม่สามารถพิสูจน์ได้

เหตุผลที่ทำไมการดำเนินการตามประเภทย่อยของคุณจึงไม่ได้เป็นการละเมิดหลักการทดแทน Liskov ค่อนข้างง่าย: ไม่มีสิ่งใดที่พิสูจน์ได้เกี่ยวกับสิ่งที่Task::Close()เกิดขึ้นจริง แน่นอนว่าProjectTask::Close()พ่นยกเว้นเมื่อStatus == Status.Startedแต่อาจจะอยู่ในStatus = Status.ClosedTask::Close()


4

ใช่มันเป็นการละเมิด

ฉันขอแนะนำให้คุณมีลำดับชั้นย้อนหลังของคุณ ถ้าไม่ใช่ทุกคนTaskสามารถปิดclose()ได้แสดงว่าไม่ได้อยู่ในTaskนั้น บางทีคุณอาจต้องการอินเทอร์เฟซCloseableTaskที่ไม่ProjectTasksสามารถนำไปใช้ได้ทั้งหมด


3
งานทุกอย่างสามารถปิดได้ แต่ไม่ได้อยู่ภายใต้ทุกสถานการณ์
Paul T Davies

วิธีการนี้ดูเหมือนว่ามีความเสี่ยงสำหรับฉันเนื่องจากผู้คนอาจเขียนโค้ดที่คาดว่า Task ทั้งหมดจะใช้ ClosableTask แม้ว่ามันจะเป็นแบบอย่างปัญหาที่ถูกต้อง ฉันฉีกขาดระหว่างวิธีการนี้กับเครื่องสถานะเพราะฉันเกลียดเครื่องรัฐ
จิมมี่ฮอฟฟา

หากTaskไม่ดำเนินการเองแล้วพวกเขากำลังทำหล่อไม่ปลอดภัยอยู่ที่ไหนสักแห่งที่จะได้เรียกCloseableTask Close()
Tom G

@TomG นั่นคือสิ่งที่ฉันกลัว
Jimmy Hoffa

1
มีเครื่องสถานะอยู่แล้ว ไม่สามารถปิดวัตถุได้เนื่องจากอยู่ในสถานะที่ไม่ถูกต้อง
Kaz

3

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

ดูเหมือนว่านี่เป็นสถานที่ที่ดีในการใช้รูปแบบสถานะสำหรับ TaskState และให้วัตถุสถานะจัดการการเปลี่ยนที่ถูกต้อง


1

ฉันหายไปที่นี่เป็นสิ่งสำคัญที่เกี่ยวข้องกับ LSP และการออกแบบโดยสัญญา - ในเงื่อนไขที่จำเป็นมันเป็นผู้โทรที่มีความรับผิดชอบคือการทำให้แน่ใจว่าจะเป็นไปตามเงื่อนไข รหัสที่เรียกว่าในทฤษฎี DbC ไม่ควรตรวจสอบเงื่อนไขเบื้องต้น สัญญาควรระบุว่าเมื่อใดที่งานสามารถปิดได้ (เช่น CanClose ส่งคืนจริง) จากนั้นรหัสการโทรควรตรวจสอบให้แน่ใจว่าเป็นไปตามเงื่อนไขก่อนที่จะเรียกปิด ()


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

@Goyo ใช่ แต่อย่างที่คนอื่น ๆ กล่าวว่ามีการยกข้อยกเว้นในประเภทย่อยซึ่งเสริมสร้างความแข็งแกร่งของเงื่อนไขและทำให้ละเมิดสัญญา (โดยนัย) ที่เรียก Close () เพียงแค่ปิดงาน
Ezoela Vacca

เงื่อนไขใด? ฉันไม่เห็นอะไรเลย
Goyo

@Goyo ตรวจสอบคำตอบที่ได้รับการยอมรับตัวอย่างเช่น: ในคลาสพื้นฐาน, Close ไม่มีเงื่อนไข, มันถูกเรียกและปิดงาน อย่างไรก็ตามในเด็กมีเงื่อนไขเบื้องต้นเกี่ยวกับสถานะที่ไม่ได้เริ่มต้น ดังที่คนอื่น ๆ ชี้ว่านี่เป็นเกณฑ์ที่แข็งแกร่งกว่าและพฤติกรรมจึงไม่สามารถทดแทนได้
Ezoela Vacca

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

0

ใช่เป็นการละเมิดที่ชัดเจนของ LSP

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

คุณสามารถลองรูปแบบการตกแต่งภายในหากคุณต้องการหลีกเลี่ยงการละเมิด LSP ในกรณีนี้ มันอาจใช้งานได้ฉันไม่รู้

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