สำรวจหลักการเปลี่ยนตัว Liskov

คำว่า SOLID เป็นคำย่อยอดนิยมที่ใช้เพื่ออ้างถึงชุดของหลักการ 5 ประการของสถาปัตยกรรมซอฟต์แวร์ ซึ่งรวมถึง: SRP (ความรับผิดชอบเดียว), เปิด / ปิด, การทดแทนของ Liskov, การแยกส่วนต่อประสานและการผกผันการพึ่งพา

LSP (Liskov Substitution Principle) เป็นหลักการพื้นฐานของ OOP และระบุว่าคลาสที่ได้รับควรจะสามารถขยายคลาสพื้นฐานได้โดยไม่ต้องเปลี่ยนพฤติกรรม กล่าวอีกนัยหนึ่งคลาสที่ได้รับควรสามารถเปลี่ยนได้สำหรับประเภทฐานกล่าวคือการอ้างอิงไปยังคลาสฐานควรสามารถแทนที่ด้วยคลาสที่ได้รับโดยไม่ส่งผลกระทบต่อพฤติกรรม หลักการทดแทน Liskov แสดงถึงการพิมพ์ย่อยเชิงพฤติกรรมที่ชัดเจนและได้รับการแนะนำโดย Barbara Liskov ในปี 2530

อ้างอิงจาก Barbara Liskov "สิ่งที่ต้องการในที่นี้คือคุณสมบัติการทดแทนต่อไปนี้: ถ้าสำหรับแต่ละอ็อบเจ็กต์ o1 ประเภท S มีอ็อบเจ็กต์ o2 ประเภท T สำหรับโปรแกรมทั้งหมดที่กำหนดไว้ในรูปของ T พฤติกรรมของ P จะไม่เปลี่ยนแปลงเมื่อ o1 ถูกแทนที่ด้วย o2 ดังนั้น S จะเป็นประเภทย่อยของ T "

ตัวอย่างคลาสสิกของการละเมิดหลักการเปลี่ยนตัว Liskov คือปัญหาสี่เหลี่ยมผืนผ้า - สี่เหลี่ยมจัตุรัส คลาส Square ขยายคลาส Rectangle และถือว่าความกว้างและความสูงเท่ากัน

พิจารณาชั้นเรียนต่อไปนี้ คลาส Rectangle ประกอบด้วยข้อมูลสองสมาชิก - ความกว้างและความสูง นอกจากนี้ยังมีคุณสมบัติสามประการ ได้แก่ ความสูงความกว้างและพื้นที่ ในขณะที่คุณสมบัติสองประการแรกตั้งค่าความสูงและความกว้างของรูปสี่เหลี่ยมผืนผ้าคุณสมบัติ Area มี getter ที่ส่งกลับพื้นที่ของสี่เหลี่ยมผืนผ้า

 class Rectangle

    {

        protected int width;

        protected int height;

         public virtual int Width

        {

            get

            {

                return width;

            }

            set

            {

                width = value;

            }

        }

 

        public virtual int Height

        {

            get

            {

                return height;

            }

            set

            {

                height = value;

            }

        }

               

       public int Area

        {

            get

            {

                return height * width;

            }

         }    

    }

สี่เหลี่ยมจัตุรัสคือรูปสี่เหลี่ยมผืนผ้าประเภทหนึ่งที่ด้านข้างมีขนาดเท่ากันกล่าวคือความกว้างและความสูงของสี่เหลี่ยมจัตุรัสเท่ากัน

class Square : Rectangle

    {

        public override int Width

        {

            get

            {

                return width;

            }

            set

            {

                width = value;

                height = value;

            }

        }

        public override int Height

        {

            get

            {

                return width;

            }

            set

            {

                width = value;

                height = value;

            }

        }

    }

 พิจารณาคลาสอื่นที่เรียกว่า ObjectFactory

 class ObjectFactory

    {

        public static Rectangle GetRectangleInstance()

        {

            return new Square();

        }

    }

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

Rectangle s = ObjectFactory.GetRectangleInstance();

s.Height = 9;

s.Width = 8;

Console.WriteLine(s.Area);

ข้อมูลโค้ดด้านบนเมื่อดำเนินการจะแสดงค่า 64 ในคอนโซล ค่าที่คาดหวังคือ 72 เนื่องจากความกว้างและความสูงที่กล่าวถึงคือ 9 และ 8 ตามลำดับ นี่เป็นการละเมิดหลักการเปลี่ยนตัว Liskov ทั้งนี้เนื่องจากคลาส Square ที่ขยายคลาส Rectangle ได้ปรับเปลี่ยนพฤติกรรม เพื่อให้แน่ใจว่าจะไม่ละเมิดหลักการการทดแทน Liskov คลาส Square สามารถขยายคลาส Rectangle ได้ แต่ไม่ควรปรับเปลี่ยนพฤติกรรม พฤติกรรมได้รับการเปลี่ยนแปลงโดยการแก้ไขตัวตั้งค่าสำหรับคุณสมบัติทั้งความกว้างและความสูง ค่าของความสูงและความกว้างจะเหมือนกันหากเป็นรูปสี่เหลี่ยมจัตุรัส - ไม่ควรเหมือนกันหากเป็นสี่เหลี่ยมผืนผ้า

เราจะแก้ไขอย่างไรกล่าวคือมั่นใจว่าหลักการนี้จะไม่ละเมิด? คุณสามารถมีคลาสใหม่ที่เรียกว่า Quadrilateral และตรวจสอบให้แน่ใจว่าทั้งคลาส Rectangle และ Square ขยายคลาส Quadrilateral

 public class Quadrilateral

    {

        public virtual int Height { get; set; }

        public virtual int Width { get; set; }

        public int Area

        {

            get

            {

                return Height * Width;

            }

        }

    } 

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

Liskov Substitution Principle เป็นส่วนเสริมของ Open Close Principle และถูกละเมิดเมื่อคุณเขียนโค้ดที่พ่น "ไม่ได้ใช้งานข้อยกเว้น" หรือคุณซ่อนเมธอดในคลาสที่ได้รับซึ่งถูกทำเครื่องหมายเป็นเสมือนในคลาสพื้นฐาน หากรหัสของคุณเป็นไปตามหลักการการทดแทน Liskov คุณจะได้รับประโยชน์มากมาย สิ่งเหล่านี้รวมถึง: การใช้งานโค้ดซ้ำ, การเชื่อมต่อที่ลดลงและการบำรุงรักษาที่ง่ายขึ้น