یکی دیگر از اصول SOLID اصل Liskov Substitution Principal نام دارد که به اختصار LSP گویند.
اصل LSP می گوید : زیر کلاسها باید بتوانند جایگزین نوع پایهی خود باشند.
مقایسه با جهان واقعی :
شغل یک پدر تجارت املاک است درحالی که پسرش دوست دارد فوتبالیست شود.
یک پسر هیچگاه نمیتواند جایگزین پدرش شود، با اینکه که آنها به یک سلسله مراتب خانوادگی تعلق دارند.
در یک مثال عمومیتر بررسی میکنیم :
به طور معمول زمانی که ما در مورد اشکال هندسی صحبت میکنیم ، مستطیل را یک کلاس پایه برای مربع میدانیم. به کد زیر توجه کنید :
public class Rectangle
{
public int Width { get; set; }
public int Height { get; set; }
}
public class Square:Rectangle
{
//codes specific to
//square will be added
}
و می توان گفت :
Rectangle o = new Rectangle();
o.Width = 5;
o.Height = 6;
بسیار خوب، اما با توجه به LSP باید قادر باشیم مستطیل را با مربع جایگزین کنیم. سعی میکنیم این کار را انجام دهیم :
Rectangle o = new Square();
o.Width = 5;
o.Height = 6;
موضوع چیست؟ مگر مربع میتواند طول و عرض نا برابر داشته باشد؟! امکان ندارد.
خوب این به چه معنی است؟ به این معنی که ما نمیتوانیم کلاس پایه را با کلاس مشتق شده جایگزین کنیم و باز هم این معنی را میدهد که ما داریم اصل LSP را نقض میکنیم.
آیا ما میتوانیم طول و عرض را در کلاس Square طبق کد زیر دوباره نویسی کنیم؟
public class Square : Rectangle
{
public override int Width
{
get{return base.Width;}
set
{
base.Height = value;
base.Width = value;
}
}
public override int Height
{
get{return base.Height;}
set
{
base.Height = value;
base.Width = value;
}
}
}
باز هم اصل LSP نقض میشود چون ما داریم رفتار خاصیتهای طول و عرض در کلاس مشتق شده را تغییر میدهیم. ولی با توجه به کد بالا یک مستطیل نمیتواند طول و عرض برابر داشته باشد چون در صورت برابری دیگر مستطیل نیست.
اما راه حل چیست؟
یک کلاس انتزاعی (abstract) را به شکل زیر ایجاد و سپس دوکلاس Square و Rectangle را از آن مشتق میکنیم :
public abstract class Shape
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
}
هم اکنون ما دو کلاس مستقل از هم داریم. یکی Square و دیگری Rectangle که هر دو از کلاس Shape مشتق شده اند.
حالا می توانیم بنویسیم :
Shape o = new Rectangle();
o.Width = 5;
o.Height = 6;
Shape o = new Square();
o.Width = 5; //both height and width become 5
o.Height = 6; //both height and width become 6
زمانی که ما در مورد اشکال هندسی صحبت می کنیم ، هیچ قاعدهی خاصی جهت اندازهی طول و عرض نیست. ممکن است برابر باشند یا نباشند.
در مثالی دیگر از اصل LPC خواهیم داشت: «فرض کنید کلاس C از کلاس B مشتق شده است، بر اساس قاعده LSP، در هر قسمت از برنامه که شئ ای از نوع B استفاده شده است، باید بتوان شئ ای از نوع C را جایگزین کرد، بدون اینکه تغییری در روند اجرای برنامه رخ دهد یا پیغام خطایی دریافت کنیم!» جا افتادن مفهوم این قاعده کمی دشوار است، اما سعی می کنیم با مثالی ساده از پیچیدگی این موضوع کم کنیم و بتوانیم توضیح شفافی از LSP ارائه دهیم. ابتدا حالتی را پیاده سازی می کنیم که قاعده LSP را نقض می کند و در قدم بعدی کد را اصلاح می کنیم که مطابق قاعده LSP باشد. کد زیر را در نظر بگیرید:
public class CollectionBase
{
public int Count { get; set; }
}
public class Array : CollectionBase
{
}
بر اساس قواعد OOP، شما از کد بالا می توانید به صورت زیر استفاده کنید:
CollectionBase collection = new Array();
var items = collection.Count;
در حقیقت، شئ Array داخل متغیری از نوع CollectionBase قرار داده شده است. خوب تا اینجا مشکلی نیست، اما فرض کنید قرار است کلاس های دیگری از CollectionBase مشتق شوند که قابلیت اضافه کردن آیتم را دارند، کلاس Array به دلیل اینکه طول ثابتی دارد نمی توان به آن آیتم جدیدی اضافه کرد. کد بالا را به صورت زیر تغییر می دهیم:
public class CollectionBase
{
public int Count { get; set; }
public virtual void Add(object item)
{
}
}
public class List : CollectionBase
{
public override void Add(object item)
{
// add item to list
}
}
public class Array : CollectionBase
{
public override void Add(object item)
{
throw new InvalidOperationException();
}
}
دقت کنید، متد Add را داخل CollectionBase تعریف کردیم، کلاس List از متد Add پشتیبانی می کند اما کلاس آرایه به دلیلی که بالا گفتیم زمان فراخوانی متد Add، ایجاد خطا می کنید:
CollectionBase array = new Array();
CollectionBase list = new List();
list.Add(2); // works
array.Add(3); // throw exception
کد بالا بدون مشکل کامپایل می شود، اما زمانی که برنامه اجرا شود، زمان اضافه کردن آیتم به آرایه پیغام خطا دریافت می کنیم، با این اوصاف کد بالا قاعده LSP را نقض کرده! زیرا همانطور که در بالا گفتیم در صورت استفاده از کلاس پایه به عنوان Data Type و قرار دادن شئ ای از نوع فرزند در آن، برنامه بدون مشکل باید کار کند. راه حل این مشکل چیست؟ به روش های مختلف می توان مشکل را حل کرد، اما در اینجا مکانیزم استفاده از interface ها را مطرح می کنیم، برای رفع مشک، کد بالا را به صورت زیر تغییر می دهیم:
public interface IList
{
void Add(object item);
}
public class CollectionBase
{
public int Count { get; set; }
}
public class List : CollectionBase, IList
{
public void Add(object item)
{
// add item to list
}
}
public class Array : CollectionBase
{
}
همانطور که مشاهده می کنید در کد بالا، متد Add، به جای تعریف در کلاس CollectionBase، داخل یک interface به نام IList تعریف شده و کلاس List این interface را پیاده سازی کرده است. با این کار، دیگر امکان فراخوانی متد Add برای کلاس Array وجود ندارد. کد بالا مبتنی بر قاعده LSP است و دیگر آن را نقض نمی کند.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید