CL jak Ćwiczenia Liskov Substitution Principle, czyli zasada podstawień Barbary Liskov

przez | 10 lutego, 2020

Liskov substitution principle – Zasada podstawienia Liskov

Oprogramowanie powinno dobrze działać, gdy w miejsce klasy bazowej podstawimy jej którąkolwiek klasę potomną.

Jak wykorzystywać w praktyce LSP?

Należy dobrze zastanowić się przed skorzystaniem z dziedziczenia. Zazwyczaj lepszą opcją jest użycie kompozycji. Warto jest również zrobić proste eksperymenty: próbować podstawić w klasach korzystających z naszego drzewa klas, wszystkie klasy potomne oraz klasę bazową i zobaczyć, czy nadal program zachowuje się odpowiednio, czy może jednak oczekuje tylko jednego konkretnego rodzaju klasy.

Przykład

Rozważmy takie zadanie:

Mamy napisać aplikację, który wyświetla obraz z kamery video. Do naszej aplikacji możemy podłączyć wiele kamer. Nie wszystkie z nich potrafimy obsłużyć. Na szczęście wiemy to już na początku. Tworzymy klasę bazową Camera oraz klasy dziedziczące po kamerze, w tym również klasę dla kamery nieobsługiwanej.

class Program
   {
       static void Main(string[] args)
       {
           var cameras = new List<Camera>();
           var cameraNC1 = new CameraNC1("Front camera");
           cameras.Add(cameraNC1);
           var cameraShower = new CameraShower(cameraNC1);
           cameraShower.Show();
 
 
           var cameraNC2 = new CameraNC2("Back camera");
           cameras.Add(cameraNC2);
           cameraShower = new CameraShower(cameraNC2);
           cameraShower.Show();
 
           var unsupportedCamera = new UnsupportedCamera(null);
           cameras.Add(unsupportedCamera);
           cameraShower = new CameraShower(unsupportedCamera);
           cameraShower.Show();
       }
   }
 
   internal class CameraShower
   {
       private Camera camera;
 
       public CameraShower(Camera camera)
       {
           this.camera = camera;
       }
 
       internal void Show()
       {
           if (!(camera is UnsupportedCamera))
               Console.WriteLine($"Camera name: {camera.GetName()}");
       }
   }
 
   internal class CameraNC2 : Camera
   {
       public CameraNC2(string name) : base(name)
       {
       }
   }
   internal class CameraNC1 : Camera
   {
       public CameraNC1(string name) : base(name)
       {
       }
 
   }
   internal class UnsupportedCamera : Camera
   {
       public UnsupportedCamera(string name) : base(name)
       {
       }
 
       internal override string GetName()
       {
           throw new Exception("This camera does not have name. It is not supported.");
       }
   }
 
   internal abstract class Camera
   {
       protected readonly string name;
 
       public Camera(string name)
       {
           this.name = name;
       }
       internal virtual string GetName()
       {
           return name;
       }
   }

Wszystko pięknie działa. Ale musieliśmy kamerę niewspieraną specjalnie obsłużyć. Jeżeli byśmy tego nie zrobili, dostalibyśmy wyjątek. Możemy trochę inaczej zaimplementować metodę GetName w klasie dla kamery niewspieranej:

class Program
{
    static void Main(string[] args)
    {
        var cameras = new List<Camera>();
        var cameraNC1 = new CameraNC1("Front camera");
        cameras.Add(cameraNC1);
        var cameraShower = new CameraShower(cameraNC1);
        cameraShower.Show();
 
 
        var cameraNC2 = new CameraNC2("Back camera");
        cameras.Add(cameraNC2);
        cameraShower = new CameraShower(cameraNC2);
        cameraShower.Show();
 
        var unsupportedCamera = new UnsupportedCamera();
        cameras.Add(unsupportedCamera);
        cameraShower = new CameraShower(unsupportedCamera);
        cameraShower.Show();
    }
}
 
internal class CameraShower
{
    private Camera camera;
 
    public CameraShower(Camera camera)
    {
        this.camera = camera;
    }
 
    internal void Show()
    {
        Console.WriteLine($"Camera name: {camera.GetName()}");
    }
}
 
internal class CameraNC2 : Camera
{
    public CameraNC2(string name) : base(name)
    {
    }
}
internal class CameraNC1 : Camera
{
    public CameraNC1(string name) : base(name)
    {
    }
 
}
internal class UnsupportedCamera : Camera
{
    public UnsupportedCamera() : base("Unknown - this camera is not supported")
    {
    }
}
 
internal abstract class Camera
{
    protected readonly string name;
 
    public Camera(string name)
    {
        this.name = name;
    }
    internal virtual string GetName()
    {
        return name;
    }
}

Wszystkie posty związane z mini projektem: Poznaj zasady SOLID i OOP:

Źródła

Obraz główny

Materiały

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *