اصل Liskov Substitution چيست؟

در سري پست هاي تعريف اصول SOLID  با

آشنا شديم در اين پست سومين اصل يعني Liskov substitution رو برسي ميكنيم.
هدف اين اصل اينه كه در ارث بري كلاس مشتق شده بايد به گونه اي طراحي بشه كه در صورت نياز با كلاس پايه ي خودش قابل تعويض باشه.
مثال ي كه تو تعاريف اين اصل معمولا مطرح ميشه
آيا مربع يك مستطيل هست؟
در منطق هندسه یک مربع نوع خاصی از مستطیل هست که دارای چهار ضلع مساوی میباشد ولي در شي گرائي داستان چيز ديگس…
در واقع ما با ارث بردن كلاس مربع از كلاس مستطيل اصل Liskov substitution رو نقض ميكنيم چون با فرض وجود متد هاي setWidth و setHeight در كلاس مستطيل ما مجبوريم اين متد ها رو به گونه ي در كلاس مربع تغيير بديم كه طول و عرض يكي بشه و از اين رو كلاس مشتق شده يعني مربع قابل تعويض با كلاس پايه يعني مستطيل نيست و…
اين اصل با اصل Open Closed ارتباط داره توجه كنيد اگه شما نتونيد از كلاس مشتق شدتون بجاي كلاس پايه استفاده كنيد در واقع و بنوعي اصل Open Closed رو رعايت نكرديد.
و اما براي مثال عملي فرض كنيد كلاسي بنام Project داريم كه كارش لود كردن و ذخيره كردن فايل هاي پروژه (از نوع ProjectFile) هست.

public class Project
{
    public Collection ProjectFiles { get; set; }

    public void LoadAllFiles()
    {
        foreach (ProjectFile file in ProjectFiles)
        {
            file.LoadFileData();
        }
    }

    public void SaveAllFiles()
    {
        foreach (ProjectFile file in ProjectFiles)
        {
           file.SaveFileData();
        }
    }
}


public class ProjectFile
{
    public string FilePath { get; set; }

    public byte[] FileData { get; set; }

    public void LoadFileData()
    {
        // خواندن فايل از ديسك
    }

    public virtual void SaveFileData()
    {
        // نوشتن فايل در ديسك
    }
}

اين كلاس كار خودش رو خوب انجام ميده تا اينكه با توجه به نياز كلاسي بنام ReadOnlyFile كه از ProjectFile مشتق شده وارد سيستم ميشه.

public class ReadOnlyFile : ProjectFile
{
    public override void SaveFileData()
    {
        throw new InvalidOperationException();
    }
}

با توجه به فقط خواندني بودن در فراخواني متد SaveFileData يك استثناء (Exception) صادر ميكنيم.

public void SaveAllFiles()
    {
        foreach (ProjectFile file in ProjectFiles)
        {
            if (file as ReadOnlyFile == null)
                file.SaveFileData();
        }
    }

مشكل بعدي ما با متد SaveAllFiles هست كه به راحتي با چك كردن نوع فايل اگه ReadOnlyFile بود ازش صرف نظر ميكنيم و بدين سان Liskov substitution رو نقض كرديم.
يكي از راه هاي تشخيص نقض كردن Liskov substitution وجود دستور شرطي “آيا اين شي از اين نوع است” در كد هست.
و اما راه حل

public class Project
{
    public Collection AllFiles { get; set; }
    public Collection WriteableFiles { get; set; }

    public void LoadAllFiles()
    {
        foreach (ProjectFile file in AllFiles)
        {
            file.LoadFileData();
        }
    }

    public void SaveAllWriteableFiles()
    {
        foreach (WriteableFile file in WriteableFiles)
        {
            file.SaveFileData();
        }
    }
}


public class ProjectFile
{
    public string FilePath { get; set; }

    public byte[] FileData { get; set; }

    public void LoadFileData()
    {
        // خواندن فايل از ديسك
    }
}


public class WriteableFile : ProjectFile
{
    public void SaveFileData()
    {
        // نوشتن فايل در ديسك
    }
}

براي رفع بجاي يك كلكسيون از 2 كلكسيون فايل هاي پروژه استفاده شده يكي شامل تمام فايل هاي پروژه و ديگري شامل فايل هاي قابل نوشتن. كلاس ProjectFile فقط شامل متد LoadFileData هست كه براي هر دونوع فايل هاي پروژه قابل استفاده هست و كلاس WriteableFile هم متد SaveFileData رو اضافه كرده.
يكي از راه هاي رعايت كردن اين اصل استفاده از اصل Interface segregation هست كه البته تو اين مثال از اينترفيس استفاده نشده و بحث سر اينه كه بي جهت كلاسي رو وادار به استفاده از متد اضافه نكنيم مثلا در همين مثال متد SaveFileData.

ادامه دارد …

نظرات

  1. علت خاصی داره که به جای as و مقایسه null نبودن، از is استفاده نکردید؟

    پاسخحذف
  2. نوشتن كد كار من نبوده (لينك دادم)
    ولي هنگام Cast كردن استفاده از as سريعتر از is هست.

    پاسخحذف

ارسال یک نظر

پست‌های معروف از این وبلاگ

مقدمه ای بر RavenDB – قسمت دوم

مقدمه ای بر RavenDB – قسمت سوم

lnav ابزاری بسیار کاربردی برای پیمایش لاگ ها در لینوکس و البته مک