← Назад к вопросам

Какой принцип Solid нарушится, при добавлении нового метода в унаследованный класс?

2.2 Middle🔥 111 комментариев
#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Нарушение SOLID принципов при добавлении метода в унаследованный класс

Вопрос касается проблемы, когда добавление нового метода в базовый класс может нарушить Liskov Substitution Principle (LSP) и Open/Closed Principle (OCP). Рассмотрим оба случая.

Нарушение Liskov Substitution Principle (LSP)

Определение: Объекты подклассов должны корректно заменять объекты базовых классов без нарушения корректности программы.

Проблемный пример:

// Базовый класс
public abstract class Bird {
    public abstract void eat();
    public abstract void fly();  // Новый метод, добавленный позже
}

// Реализация для летающих птиц
public class Eagle extends Bird {
    @Override
    public void eat() {
        System.out.println("Eagle eats meat");
    }
    
    @Override
    public void fly() {
        System.out.println("Eagle flies high");
    }
}

// Реализация для нелетающих птиц
public class Penguin extends Bird {
    @Override
    public void eat() {
        System.out.println("Penguin eats fish");
    }
    
    @Override
    public void fly() {
        // ПРОБЛЕМА: пингвин не летает!
        // LSP НАРУШЕН
        throw new UnsupportedOperationException("Penguins cannot fly");
    }
}

// Использование
public class AnimalSanctuary {
    public static void main(String[] args) {
        List<Bird> birds = new ArrayList<>();
        birds.add(new Eagle());
        birds.add(new Penguin());
        
        // LSP НАРУШЕН: не все Bird могут летать
        for (Bird bird : birds) {
            bird.fly();  // Выбросит exception для Penguin
        }
    }
}

Почему это нарушение LSP:

  • Клиент ожидает, что любой Bird может летать
  • Но Penguin выбрасывает UnsupportedOperationException
  • Подкласс не может корректно заменить базовый класс

Правильное решение: тщательная иерархия классов

// Правильно: разные иерархии для разных поведений
public abstract class Bird {
    public abstract void eat();
}

public abstract class FlyingBird extends Bird {
    public abstract void fly();
}

public class Eagle extends FlyingBird {
    @Override
    public void eat() {
        System.out.println("Eagle eats meat");
    }
    
    @Override
    public void fly() {
        System.out.println("Eagle flies high");
    }
}

public class Penguin extends Bird {
    @Override
    public void eat() {
        System.out.println("Penguin eats fish");
    }
    // Penguin НЕ наследует fly()
}

// Правильное использование
public class AnimalSanctuary {
    public static void main(String[] args) {
        List<Bird> birds = new ArrayList<>();  // Все птицы
        List<FlyingBird> fliers = new ArrayList<>();  // Только летающие
        
        birds.add(new Eagle());
        birds.add(new Penguin());
        
        fliers.add(new Eagle());
        // fliers.add(new Penguin());  // Ошибка типа — LSP соблюдён!
        
        // Безопасно работаем с летающими птицами
        for (FlyingBird bird : fliers) {
            bird.fly();  // Всегда работает
        }
    }
}

Нарушение Open/Closed Principle (OCP)

Определение: Класс должен быть открыт для расширения, но закрыт для модификации.

Проблемный пример:

// Базовый класс (закрытый для модификации)
public abstract class PaymentProcessor {
    public abstract void processPayment(double amount);
    
    public void logTransaction(String details) {
        System.out.println("[LOG] " + details);
    }
}

// Клиент использует базовый класс
public class OrderService {
    private PaymentProcessor processor;
    
    public OrderService(PaymentProcessor processor) {
        this.processor = processor;
    }
    
    public void checkout(double amount) {
        processor.processPayment(amount);
        // Надеемся на базовый класс
    }
}

// Позже добавляют новый метод в базовый класс
public abstract class PaymentProcessor {
    public abstract void processPayment(double amount);
    
    public void logTransaction(String details) {
        System.out.println("[LOG] " + details);
    }
    
    // НОВЫЙ МЕТОД - все подклассы должны реализовать!
    public abstract void validatePaymentMethod();  // OCP НАРУШЕН
}

// Теперь все реализации сломаны и требуют изменения
public class CreditCardProcessor extends PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing credit card payment: " + amount);
    }
    
    @Override
    public void logTransaction(String details) {
        super.logTransaction(details);
    }
    
    // НОВЫЙ МЕТОД - приходится добавлять в ВСЕ подклассы
    @Override
    public void validatePaymentMethod() {
        System.out.println("Validating credit card...");
    }
}

Правильное решение для OCP: использование интерфейсов

// Интерфейс вместо абстрактного класса
public interface PaymentProcessor {
    void processPayment(double amount);
}

// Отдельный интерфейс для валидации
public interface PaymentValidator {
    void validatePaymentMethod();
}

// Реализация комбинирует необходимые интерфейсы
public class CreditCardProcessor implements PaymentProcessor, PaymentValidator {
    @Override
    public void processPayment(double amount) {
        validatePaymentMethod();
        System.out.println("Processing credit card payment: " + amount);
    }
    
    @Override
    public void validatePaymentMethod() {
        System.out.println("Validating credit card...");
    }
}

// Простая реализация, не требующая валидации
public class CashProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing cash payment: " + amount);
    }
    // НЕ реализует PaymentValidator - OCP СОБЛЮДЁН
}

Реальный пример нарушения

// Плохая иерархия
public class DataProcessor {
    public void processData() {
        System.out.println("Processing...");
    }
    
    // Добавили новый метод - все подклассы должны его реализовать
    public void exportToXML() {
        // Пустая реализация или throw Exception
    }
}

public class CSVDataProcessor extends DataProcessor {
    @Override
    public void processData() {
        System.out.println("Processing CSV");
    }
    
    @Override
    public void exportToXML() {
        // CSVDataProcessor не нуждается в XML экспорте
        // Но должен реализовать метод - НАРУШЕНИЕ
        throw new UnsupportedOperationException();
    }
}

Правильное решение: Interface Segregation

// Маленькие специализированные интерфейсы
public interface Processor {
    void processData();
}

public interface XmlExporter {
    void exportToXML();
}

public interface JsonExporter {
    void exportToJson();
}

// Реализация выбирает нужные интерфейсы
public class CSVDataProcessor implements Processor {
    @Override
    public void processData() {
        System.out.println("Processing CSV");
    }
    // Не реализует экспортеры - ISP СОБЛЮДЁН
}

public class DatabaseDataProcessor implements Processor, XmlExporter, JsonExporter {
    @Override
    public void processData() {
        System.out.println("Processing Database");
    }
    
    @Override
    public void exportToXML() {
        System.out.println("Exporting to XML");
    }
    
    @Override
    public void exportToJson() {
        System.out.println("Exporting to JSON");
    }
}

Ещё один пример: добавление метода в наследованный класс

// Исходный класс
public class Repository {
    public void save(Entity entity) {
        System.out.println("Saving entity");
    }
}

// Подкласс
public class UserRepository extends Repository {
    // Работает прекрасно
}

// Позже добавляют новый метод
public class Repository {
    public void save(Entity entity) {
        System.out.println("Saving entity");
    }
    
    // Новый метод - может сломать подклассы
    public void batchSave(List<Entity> entities) {
        for (Entity e : entities) {
            save(e);
        }
    }
}

Это нарушает Open/Closed Principle потому что:

  • Базовый класс должен быть закрыт для модификации
  • Подклассы не контролировали добавление нового метода
  • Если подкласс переопределяет save(), batchSave() может работать неправильно

Решение: использовать шаблонный метод

public class Repository {
    public final void save(Entity entity) {
        validateEntity(entity);
        doSave(entity);
        logSave(entity);
    }
    
    // Переопределяемый метод
    protected abstract void doSave(Entity entity);
    
    protected void validateEntity(Entity entity) {}
    
    protected void logSave(Entity entity) {
        System.out.println("Entity saved");
    }
    
    public final void batchSave(List<Entity> entities) {
        for (Entity e : entities) {
            save(e);
        }
    }
}

Заключение

При добавлении нового метода в унаследованный класс нарушаются:

  1. Liskov Substitution Principle (LSP) — если подкласс не может правильно реализовать новый метод и выбрасывает исключение

  2. Open/Closed Principle (OCP) — базовый класс модифицируется, заставляя менять все подклассы

Решение:

  • Используй интерфейсы вместо наследования
  • Применяй Interface Segregation Principle (множество маленьких интерфейсов)
  • Раздели иерархию классов на более специализированные
  • Используй шаблонные методы для гибкости
  • Расширяй через композицию, а не наследование