جمعه , ۲۸ تیر ۱۳۹۸
خانه - #C - آشنایی با SOLID در شی گرایی
Solid
Solid

آشنایی با SOLID در شی گرایی

SOLID یک مفهوم در شی گرایی می باشد. از این مفهوم بیشتر به عنوان یکی از مجموعه قوانین شی گرایی یاد می شود. این واژه از به هم پیوستن عبارات زیر به وجود آمده است.هر یک از عبارات یک قانون را در این رابطه بیان می کنند که  در ادامه هر کدام را به شکل کامل تری توضیح خواهیم داد.

  1. Single Responsibility Principle
  2. Open Closed Principle
  3. Liskov Substitution Principle
  4. Interface Segregation Principle
  5. Dependency Inversion Principle

قانون اول : تک وظیفه ای بودن

بر اساس این قانون هر ماژول یا کلاس باید دقیقا یک وظیفه را بر عهده بگیرد و این وظیفه باد به طور کاملا صحیحی در داخل کلاس کپسوله شود. این قانون را به اختصار SRP می نامند. بر اساس این قانون نباید یک کلاس بیشتر از یک وظیفه را در بر بگیرد. و یک وظیفه نباید در بین چند کلاس یا ماژول مختلف توزیع گردد.

کد زیر یک نمونه از نمونه هایی است که این اصل در آن رعایت نشده است.دقت کنید که کلاس ServiceStation هر سه عملیات باز کردن گیت ، انجام عملیات و بسته شدن گیت را انجام می دهد.

 

public class ServiceStation
{
    public void OpenGate()
    {
        //Open the gate if the time is later than 9 AM
    }

    public void DoService(Vehicle vehicle)
    {
        //Check if service station is opened and then
        //complete the vehicle service
    }
 
    public void CloseGate()
    {
        //Close the gate if the time has crossed 6PM
    }
}

 

بازنویسی شده کد بالا کد زیر خواهد بود که در آن یک اینترفیس به نام IGateUtility اضافه شده است که عملیات مربوط به گیت در آن قرار داده شده است. و کلاس جداگانه ای به نام ServiceStationUtility قوانین اینتر فیس را پیاده می کند و عملیات های مربوط به گیت را پیاده سازی می کند. و کلاس ServiceStation وظیفه انجام سرویس را با کمک کلاس ServiceStationUtility پیاده سازی می کند.

 

public class ServiceStation
{
 IGateUtility _gateUtility;
 
 public ServiceStation(IGateUtility gateUtility)
 {
 this._gateUtility = gateUtility;
 }
 public void OpenForService()
 {
 _gateUtility.OpenGate();
 }
 
 public void DoService()
 {
 //Check if service station is opened and then
 //complete the vehicle service
 }
 
 public void CloseForDay()
 {
 _gateUtility.CloseGate();
 }
}
 
public class ServiceStationUtility : IGateUtility
{
 public void OpenGate()
 {
 //Open the shop if the time is later than 9 AM
 }
 
 public void CloseGate()
 {
 //Close the shop if the time has crossed 6PM
 }
}
 
 
public interface IGateUtility
{
 void OpenGate();
 void CloseGate();
}

 

قانون دوم : باز و بسته بودن

بر اساس این قانون باید سورس اپلیکیشن ما باید در برابر توسعه پذیری باز و در برابر شکسته شدن و تغییرات بسته باشد. به این صورت که کدی که نوشته این را بتوان بدون اینکه در ساختار اصلی تغییری ایجاد کرد توسعه داد.

بر اساس OCP کد باید به سادگی توسعه یابد بدون اینکه هسته پیاده شده برای آن تغییر کند.کد زیر این مشکل را به وضوح نمایش می دهد. کلاس حاوی لیستی از اطلاعات خودروهاست که با متد MileageCalculator مقدار طی شده توسط هر خودرو را نمایش می دهد. هنگامی ک نیاز داریم تا ماشین جدیدی به  سیستم اضافه شود باید کد اصلی برنامه را تغییر دهیم.

 

public class MileageCalculator
{
    IEnumerable<Car> _cars;
    public MileageCalculator(IEnumerable<Car> cars) { this._cars = cars; }

    public void CalculateMileage()
    {
        foreach (var car in _cars)
        {
            if (car.Name == "Audi")
                Console.WriteLine("Mileage of the car {0} is {1}", car.Name, "10M");
            else if (car.Name == "Mercedes")
                Console.WriteLine("Mileage of the car {0} is {1}", car.Name, "20M");
        }
    }
}

 

برای رفع این ایراد که اصل OCP را رعایت نکرده است ابتدا یک اینترفیس از نوع Car میسازیم و سپس انواع مختلف خودرو هایی را که لازم داریم به صورت کلاس می سازیم و در کلاس CarController  که به عنوان نگه دارنده لیست خودرو ها عمل می کند خود رو ها را به لیستمان اضافه می کنیم. و در نهایت کلاس MileageCalculator را بر اساس ICar و CarController باز نویسی می کنیم.

 

public class MileageCalculator
{
    IEnumerable<Car> _cars;
    public MileageCalculator(IEnumerable<Car> cars) { this._cars = cars; }
 
    public void CalculateMileage()
    {
        CarController controller = new CarController();
        foreach (var car in _cars)
        {
                Console.WriteLine("Mileage of the car {0} is {1}", car.Name, controller.GetCarMileage(car.Name));
        }
    }
}
 
public class CarController
{
    List<ICar> cars;
    public CarController()
    {
        cars = new List<ICar>();
        cars.Add(new Audi());
        cars.Add(new Mercedes());
    }
 
    public string GetCarMileage(string name)
    {
        return cars.First(car => car.Name == name).GetMileage();
    }
}

public interface ICar
{
    string Name { get; set; }
    string GetMileage();
}
 
public class Audi : ICar
{
    public string Name { get; set; }

    public string GetMileage()
    {
        return "10M";
    }
}


public class Mercedes : ICar
{
    public string Name { get; set; }
 
    public string GetMileage()
    {
        return "20M";
    }
}

قانون سوم:جایگزینی Liskov

بر اساس این اصل LSP کلاسی که از یک بیس کلاس ارث بری کرده است باید بتواند در کد به جای آن قرار بگیرد. به عنوان مثال اگر کلاس B از کلاس A ارث بری کرده باشد. باید بتواند در عبارات به جای کلاس A جایگزین شود.

در کد زیر این اصل رعایت نشده است چون نمیتوان یک نمونه از سیب را با پرتقال مقدار دهی کرد.

 

namespace SolidDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Apple apple = new Orange();
            Console.WriteLine(apple.GetColor());
        }
    }
 
    public class Apple
    {
        public virtual string GetColor()
        {
            return "Red";
        }
    }
 
    public class Orange : Apple
    {
        public override string GetColor()
        {
            return "Orange";
        }
    }
}

 

در این اصل باید همواره دقت داشته باشیم که منطق درستی از انتزاع را پیاده سازی کنیم تا به کد هایی همانند کد بالا بر نخوریم. برای حل این مشکل یک بیس کلاس به نام Fruit ساخته این که سیب و پرتقال از آن ارث بری می کنند و متد آن را برای خود باز نویسی می کنند.

namespace SolidDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Fruit fruit = new Orange();
            Console.WriteLine(fruit.GetColor());
            fruit = new Apple();
            Console.WriteLine(fruit.GetColor());
        }
    }
 
    public abstract class Fruit
    {
        public abstract string GetColor();
    }
 
    public class Apple : Fruit
    {
        public override string GetColor()
        {
            return "Red";
        }
    }
 
    public class Orange : Apple
    {
        public override string GetColor()
        {
            return "Orange";
        }
    }
}

قانون چهارم: تفکیک روابط

این اصل ISP بیانگر این است که هیچ کلاس کلاینتی نباید مجبور شود متدی را پازنویسی کند که به آن نیازی ندارد.

به عنوان مثال وقتی یک اینترفیس با تعداد زیادی متد تعریف می شود.کلاس هایی که از اینترفیس پیروی می کنند مجبورند متد هایی رابازنویسی کنند که نیازی به آنها ندارند.در مثال زیر که اصل ISP در آن نقض شده دقت کنید.کلاس InpersonOrder متد ProcessCreditCard را بازنویسی کرده که اصلا به آن نیاز ندارد.زیر مشتریانی که سفارش را نقدا حساب می کنند نیازی به ثبت اطلاعات کارت اعتباری ندارند.

 

public interface IOrder
    {
        void Purchase();
        void ProcessCreditCard();
    }
 
    public class OnlineOrder : IOrder
    {
        public void Purchase()
        {
            //Do purchase
        }
 
        public void ProcessCreditCard()
        {
            //process through credit card
        }
    }
 
    public class InpersionOrder : IOrder
    {
        public void Purchase()
        {
            //Do purchase
        }
 
        public void ProcessCreditCard()
        {
            //Not required for inperson purchase
            throw new NotImplementedException();
        }
    }

 

این مشکل را با تقسیم کردن اینترفیس حل می کنیم. به جای یک اینترفیس دوتا اینترفیس با نام های IOrder و IOnlineOrder می سازیم که درآن فقط مشتریهای آنلاین لازم دارند اطلاعات کارت اعتباری خود را ثبت کنند.

 

public interface IOrder
    {
        void Purchase();
    }

    public interface IOnlineOrder
    {
        void ProcessCreditCard();
    }
    public class OnlineOrder : IOrder, IOnlineOrder
    {
        public void Purchase()
        {
            //Do purchase
        }
         public void ProcessCreditCard()
        {
            //process through credit card
        }
    }

    public class InpersionOrder : IOrder
    {
        public void Purchase()
        {
            //Do purchase
        }
    }

قانون پنجم :وارونگی وابستگی ها

DIP می گوید که کلاس های سطح بالا یا پدر نباید وابسته به کلاس های سطح پایین یا فرزند باشند. بلکه این رابطه ها باید به وسیله کلاس های انتزاعی (اینترفیس ها) پیاده سازی شود.برای اطلاعات بیشتر در رابطه با IOC یا Inversion Of Control می توانید به مقاله Inversion Of Control مراجعه کنید.

منبع

درباره ی محمد لطفی

برنامه نویس و توسعه دهنده .Net هستم. از یادگیری و آموزش لذت می برم. برنامه نویسی رو از دانشگاه شروع کردم و الانم در نیک آموز مهارت های خودم رو توسعه می دم.

همچنین ببینید

چرخه عمر درخواست ها در ASP MVC

در این مقاله به بررسی طول عمر و اتفاقاتی که برای یک درخواست Http از …

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *