SOLID – Zasada podstawienia Liskov

Definicja

Zasada podstawienia Liskov (Liskov Substitution Principle – LSP) mówi, że obiekty klasy pochodnej powinny być w stanie zastąpić obiekty klasy bazowej bez zmiany poprawności działania programu. Oznacza to, że podklasa musi przestrzegać kontraktów swojej klasy bazowej – nie może łamać jej oczekiwań ani gwarancji.

Przykład programu przed wdrożeniem LSP

public class Employee {
    public void CalculateSalary() {
        System.out.println("Calculate for Employee");
    }

    public void ShowIdCard() {
        System.out.println("Show card Employee");
    }
}
public class Ceo extends Employee {
    @Override
    public void CalculateSalary() {
        System.out.println("Calculate for Ceo");
    }

    @Override
    public void ShowIdCard() {
        System.out.println("Show card Ceo");
    }
}
public class Programmer extends Employee {
    @Override
    public void CalculateSalary() {
        System.out.println("Calculate for Programmer");
    }

    @Override
    public void ShowIdCard() {
        System.out.println("Show card Programmer");
    }
}
public class Volunteer extends Employee {
    @Override
    public void CalculateSalary() {
        throw new UnsupportedOperationException("Volunteers do not receive a  
        salary");
    }
}

    @Override
    public void ShowIdCard() {
        System.out.println("Show card Volunteer");
    }
}
public class Finances {
    public void PaySalaries(Employee[] employees) {
        for(Employee employee : employees) {
            employee.CalculateSalary();
        }
    }
}
public class Hr {
    public void GetIdCards(Employee[] employees) {
        for(Employee employee : employees) {
            employee.ShowIdCard();
        }
    }
}
public class Program {
    public static void main(String[] args) {
        Finances finances = new Finances();
        Hr hr = new Hr();

        Employee[] employees = new Employee[] {new Programmer(), new Ceo(),  
        new Volunteer()};

        finances.PaySalaries(employees);
        hr.GetIdCards(employees);
    }
}

Klasa Volunteer rozszerza klasę Employee, ale nie spełnia kontraktu – metoda CalculateSalary() rzuca wyjątek, co łamie oczekiwania względem klasy Employee. Oznacza to, że Volunteer nie może być bezpiecznie użyta tam, gdzie oczekuje się Employee.

Kod po zastosowaniu zasady LSP

Zastosujemy interfejsy dla rozdzielenia zachowań: tylko niektórzy pracownicy są opłacani, ale wszyscy mogą mieć identyfikator.

public interface IPayableEmployee {
    void CalculateSalary();
}
public interface IVisitor {
    void ShowIdCard();
}
public class Ceo implements IVisitor, IPayableEmployee{

    @Override
    public void CalculateSalary() {
        System.out.println("Calculate for Ceo");
    }

    @Override
    public void ShowIdCard() {
        System.out.println("Show card Ceo");
    }
}
public class Programmer implements IVisitor, IPayableEmployee{
    @Override
    public void CalculateSalary() {
        System.out.println("Calculate for Programmer");
    }

    @Override
    public void ShowIdCard() {
        System.out.println("Show card Programmer");
    }
}
public class Volunteer implements IVisitor{
    @Override
    public void ShowIdCard() {
        System.out.println("Show card Volunteer");
    }
}
public class Finances {
    public void PaySalaries(IPayableEmployee[] iPayableEmployees) {
        for(IPayableEmployee iPayableEmployee : iPayableEmployees) {
            iPayableEmployee.CalculateSalary();
        }
    }
}
public class Hr {
    public void GetIdCards(IVisitor[] iVisitors) {
        for(IVisitor iVisitor : iVisitors) {
            iVisitor.ShowIdCard();
        }
    }
}
public class Program {
    public static void main(String[] args) {
        Finances finances = new Finances();
        Hr hr = new Hr();

        Ceo ceo = new Ceo();
        Programmer programmer = new Programmer();
        Volunteer volunteer = new Volunteer();

        IPayableEmployee[] iPayableEmployees = new IPayableEmployee[] {ceo,  
        programmer};
        IVisitor[] iVisitors = new IVisitor[] {ceo, programmer, volunteer};

        finances.PaySalaries(iPayableEmployees);
        hr.GetIdCards(iVisitors);
    }
}

Podsumowanie

Stosując zasadę podstawienia Liskov:

  • Projektujemy hierarchie klas, które są bezpiecznie zamienne.
  • Oddzielamy zachowania za pomocą interfejsów (np. IPayableEmployee, IVisitor).
  • Unikamy sytuacji, w których podklasy łamią oczekiwania klasy bazowej.

W przykładzie, poprzez zastosowanie interfejsów, program staje się bardziej odporny na błędy i łatwiejszy do rozwijania.

Tagi: Brak tagów

Add a Comment

Your email address will not be published. Required fields are marked *