اصول طراحی SOLID در #C (قسمت سوم)

در این مطلب میخواهیم در مورد اصل سوم SOLID یعنی Liskov Substitution Principle صحبت کنیم
Liskov Substitution Principle
تعریف : هر تابع یا کدی که از اشاره گر یا ارجاع به کلاس پایه استفاده می کند باید بتواند از هر کلاسی که از آن کلاس پایه مشتق شده است نیز بدون هیچ گونه تغییری استفاده کند.
این اصل پیشنهاد می کند که کلاس های مشتق شده خود را به گونه ای بنویسید که هر کلاس فرزند (کلاس مشتق شده) باید کاملاً به جای کلاس والد خود (کلاس پایه) ،بدون تغییر رفتار قابل تعویض باشد.
این اصل می گوید که اگر تابعی در کلاس پایه دارید که در کلاس مشتق شده نیز وجود دارد، کلاس مشتق شده باید آن تابع را با همان رفتار پیاده سازی کند، یعنی باید همان خروجی را برای ورودی داده شده بدهد. اگر رفتار در کلاس مشتق شده یکسان باشد، کد کلاینت با استفاده از تابع کلاس پایه می تواند با خیال راحت از همان تابع از کلاس های مشتق شده بدون هیچ تغییری استفاده کند. بنابراین هر تابعی از کلاس پایه که توسط کلاس مشتق شده نادیده گرفته می شود باید دارای امضای یکسان باشد، یعنی باید همان مقادیر ورودی را بپذیرد و همچنین باید همان مقدار را برگرداند. تابع در کلاس مشتق شده نباید قوانین سخت گیرانه تری را اجرا کند زیرا اگر با یک شی از کلاس پایه فراخوانی شود باعث ایجاد مشکل می شود. می توان گفت این اصل به نوعی گسترش دهنده اصل Open/Closed است که از وراثت پشتیبانی می کند و اصل جایگزینی لیسکوف این وراثت را یک قدم جلوتر می برد و بیان می کند که کلاس های مشتق شده می توانند کلاس پایه را گسترش دهند اما رفتار را ثابت نگه دارند. این اصل بیشتر از ساختار این کلاس ها بر رفتار کلاس های پایه و توسعه یافته تمرکز دارد.
به کلاس زیر نگاهی بیندازید که تابعی برای خواندن رشته اتصال پایگاه داده از فایل JSON دارد و آن را برمی گرداند.
public class ReadParameters
{
public virtual string GetDbConnString()
{
string dbConn = "Connection String From JSON File";
//Read json setting file to get Connection String
dbConn = ParseServerDetails(dbConn);
return dbConn;
}
public string ParseServerDetails(string DbConn)
{
return DbConn + " - Parsed";
}
}
بعداً، این شرط را دریافت می کنیم که رشته اتصال پایگاه داده می تواند در فایل XML نیز وجود داشته باشد، بنابراین باید کدی را برای خواندن آن از فایل XML نیز اضافه کنیم. بنابراین یک کلاس دیگر را مانند شکل زیر اضافه کردیم تا رشته اتصال را از یک فایل XML بخوانیم.
public class ReadParametersFromXML : ReadParameters
{
public override string GetDbConnString()
{
string dbConn = "Connection String From XML File";
//Read XML file to get Connection String
dbConn = ParseServerDetails(dbConn);
return dbConn;
}
}
اکنون به کد زیر نگاه کنید که در آن شیء کلاس پایه (ReadParameters) را نمونه سازی کرده ایم و سپس آن را با شیء کلاس مشتق شده (ReadParametersFromXML) جایگزین کرده ایم تا رشته اتصال پایگاه داده را از فایل بخوانیم.
class Program
{
static void Main(string[] args)
{
ReadParameters readParameters = new ReadParameters();
Console.WriteLine(readParameters.GetDbConnString());
readParameters = new ReadParametersFromXML();
Console.WriteLine(readParameters.GetDbConnString());
Console.ReadKey();
}
}
با اجرای کد بالا خروجی های زیر را دریافت می کنیم :
Connection String From JSON File - Parsed
Connection String From XML File - Parsed
از خروجی بالا می بینیم که اگر کلاس پایه را با کلاس مشتق شده جایگزین کنیم، رشته اتصال پایگاه داده را به جای فایل JSON مانند کلاس پایه، از فایل XML برمی گرداند. اکنون اگر ورودی های رشته اتصال پایگاه داده در فایل JSON و XML متفاوت باشد، این جایگزینی می تواند رفتار را تغییر دهد. این نقض اصل جایگزینی لیسکوف در اصول SOLID است.
اکنون مشکل را به صورت زیر حل میکنیم :
public abstract class ReadParameters
{
public abstract string GetDbConnString();
public string ParseServerDetails(string DbConn)
{
return DbConn + " - Parsed";
}
}
public class ReadParametersFromXML : ReadParameters
{
public override string GetDbConnString()
{
string dbConn = "Connection String From XML File";
//Read XML file to get Connection String
dbConn = ParseServerDetails(dbConn);
return dbConn;
}
}
public class ReadParametersFromJSON : ReadParameters
{
public override string GetDbConnString()
{
string dbConn = "Connection String From JSON File";
//Read XML file to get Connection String
dbConn = ParseServerDetails(dbConn);
return dbConn;
}
}
اکنون به کد زیر نگاه کنید که در آن شیء کلاس پایه را اعلان کرده اما شی کلاس مشتق شده را برای خواندن رشته اتصال پایگاه داده از فایل نمونه سازی کرده ایم.
static void Main(string[] args)
{
ReadParameters readParameters = new ReadParametersFromXML();
Console.WriteLine(readParameters.GetDbConnString());
readParameters = new ReadParametersFromJSON();
Console.WriteLine(readParameters.GetDbConnString());
Console.ReadKey();
}
پس از اجرای برنامه فوق، خروجی را مطابق زیر دریافت می کنیم
Connection String From XML File - Parsed
Connection String From JSON File - Parsed
بنابراین ما پیاده سازی را مطابق اصل جایگزینی Liskov در اصول SOLID با انتزاعی کردن کلاس پایه و تعریف تابع GetDbConnString به صورت abstract تصحیح کردیم.
مزایا
- اگر به اشتباه شخصی کلاس پایه را با کلاس مشتق شده جایگزین کرده باشد، از تولید خطا جلوگیری می کند زیرا رفتارش تغییر نخواهد کزد.
- در حالت عادی کلاس های مشتق شده به راحتی می توانند خطاهایی را برای متد ایجاد کنند.
سخن پایانی
این اصل به طور خلاصه راهنمایی هایی در مورد نحوه استفاده از وراثت در زبان های شی گرا ارائه می دهد که بیان می کند همه کلاس های مشتق شده باید به همان شیوه کلاس پایه رفتار کنند. از نظر عملی، اجرای این اصل را کمی دشوار میدانند، یعنی نیاز به برنامهریزی و تلاشهای زیادی برای طراحی کد درست در شروع پروژه دارد.
همچنین، هیچ ابزاری برای اطمینان از این اصل کمک نمیکند و شما باید بررسیهای دستی ، بازبینی کد یا آزمایش کد را انجام دهید تا مطمئن شوید که کد، اصل جایگزینی Liskov در اصول SOLID را نقض نمیکند.
دیدگاهتان را بنویسید