← Назад к вопросам
Как нарушить принцип Лисков
3.0 Senior🔥 161 комментариев
#SOLID и паттерны проектирования#ООП
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как нарушить принцип Лисков
Принцип подстановки Лисков (Liskov Substitution Principle, LSP) - это один из принципов SOLID. Он гласит: объекты подклассов должны корректно заменять объекты родительского класса, не нарушая логики программы. Давай посмотрим, как его нарушить.
Нарушение 1: Изменение контракта в подклассе
// Базовый класс
public class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
// Подкласс нарушает LSP
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // Квадрат! Ширина = высота
}
@Override
public void setHeight(int height) {
this.width = height; // Нарушение контракта!
this.height = height;
}
}
// Проблема:
public void testArea(Rectangle rect) {
rect.setWidth(5);
rect.setHeight(10);
assert rect.getArea() == 50; // Ожидаем 50
}
Rectangle rect = new Square(); // Используем Square как Rectangle
testArea(rect); // ПАДАЕТ! Square вернёт 100, а не 50
Нарушение 2: Выброс неожиданных исключений
public class PaymentService {
public void processPayment(double amount) {
// Контракт: выполнить платёж
}
}
public class CreditCardPaymentService extends PaymentService {
@Override
public void processPayment(double amount) {
// Нарушение: выбрасываем исключение, которого не было в базовом классе
if (amount < 0) {
throw new IllegalArgumentException("Amount must be positive");
}
// Это нарушение LSP, если базовый класс не объявляет это исключение
}
}
// Клиентский код не ожидает исключение
public void handlePayment(PaymentService service) {
service.processPayment(-10); // Не ожидает IllegalArgumentException!
}
Нарушение 3: Ослабление предусловий
public class BankAccount {
// Контракт: деньги снимаются только если достаточно средств
public void withdraw(double amount) {
if (amount > balance) {
throw new InsufficientFundsException();
}
balance -= amount;
}
}
public class UnlimitedAccount extends BankAccount {
@Override
public void withdraw(double amount) {
// Нарушение: переопределяем условие
// Теперь можем снять сколько угодно (включая отрицательные суммы)
balance -= amount; // Даже если amount > balance!
}
}
// Проблема:
public void transferMoney(BankAccount account, double amount) {
account.withdraw(amount); // Ожидаем, что выбросится исключение если не хватает денег
}
BankAccount account = new UnlimitedAccount();
account.deposit(100);
transferMoney(account, 500); // Не выбросит исключение! LSP нарушен
Нарушение 4: Усиление постусловий (ослабление результата)
public class DataRepository {
// Контракт: вернуть список пользователей (никогда null)
public List<User> getAllUsers() {
return new ArrayList<>(); // Минимум пустой список
}
}
public class CachedDataRepository extends DataRepository {
@Override
public List<User> getAllUsers() {
if (cacheExpired) {
return null; // Нарушение! Клиент не ожидает null
}
return cache;
}
}
// Проблема:
DataRepository repo = new CachedDataRepository();
List<User> users = repo.getAllUsers();
for (User user : users) { // NullPointerException!
System.out.println(user.getName());
}
Нарушение 5: Изменение поведения типов исключений
public class DatabaseConnection {
public void connect() throws DatabaseException {
// Контракт: выбросит DatabaseException
}
}
public class MySQLConnection extends DatabaseConnection {
@Override
public void connect() throws SQLException {
// Нарушение: выбросит SQLException вместо DatabaseException
// Клиент не ловит SQLException!
}
}
public void establishConnection(DatabaseConnection conn) {
try {
conn.connect();
} catch (DatabaseException e) { // Ловим DatabaseException
// Но если это MySQLConnection, выбросит SQLException!
}
}
Как НЕ нарушать LSP
Правильный вариант: Composition вместо Inheritance
public class Square {
private int side;
public void setSide(int side) {
this.side = side;
}
public int getArea() {
return side * side;
}
}
public class Rectangle {
private int width;
private int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
// Вместо наследования используем общий интерфейс
public interface Shape {
int getArea();
}
public class Square implements Shape {
public int getArea() { /* ... */ }
}
public class Rectangle implements Shape {
public int getArea() { /* ... */ }
}
Ключевые моменты LSP
- Подклассы должны расширять функциональность, а не изменять её
- Не ослабляйте предусловия (требования к входным параметрам)
- Не усиливайте постусловия (требования к результатам)
- Не выбрасывайте неожиданные исключения
- Сохраняйте инварианты (постоянные свойства)
Вывод
Принцип Лисков нарушается, когда подкласс не может корректно заменить родительский класс. Это приводит к ошибкам, которые сложно отследить. Лучше использовать Composition и Interface Segregation вместо наследования, когда есть сомнения.