Рубрики
Без рубрики

Если это сработает, не меняйте его – Откройте Закрытый Принцип

Принцип открытого закрытия гласит, что программные объекты (классы, модули, функции и т.д.) должны… С тегами: ооп, java, качество кода, новички.

Принцип Открытого закрытого гласит, что

Программные объекты (классы, модули, функции и т.д.) должны быть открыты для расширения, но закрыты для модификации.

Цель здесь состоит в том, чтобы написать свой код таким образом, чтобы расширение его функциональности не требовало от вас изменения существующего кода. Главный мотив – избежать неожиданных ошибок, которые могут возникнуть в результате изменения поведения вашего класса или метода.

Почему бы просто не внести несколько изменений?

Давайте рассмотрим небольшой пример. Ниже приведен класс, который дает мне изображения для продукта. Один из его методов получает изображения продукта на основе идентификатора продукта.

class ProductImageProvider {
    public List getProductImagesByProductId(String productId) {
        //get images where productId = productId
    }
        ...
}

Когда мы писали этот код, ожидалось, что он будет вести себя только одним способом и выдаст мне все изображения. Но ваша система столкнулась с небольшой ошибкой – в списке продуктов рядом с некоторыми продуктами есть странное пейзажное изображение. Разработчик 1 (новичок) идет дальше и исследует, как страница листинга получает изображения продукта. Он находит этот класс и вносит небольшое изменение в наш метод. Теперь он возвращает только изображения, подходящие для списка продуктов.

class ProductImageProvider {
    public List getProductImagesByProductId(String productId) {
        //get images where "productId" = productId AND "view" = "listing"
    }
        ...
}

Внесена большая ошибка – Более опытный разработчик знал бы, что этот метод вызывается из нескольких мест, и в этих местах могут потребоваться разные типы изображений. В любом случае, в этом случае ошибка исправляется, но теперь на странице отображения продукта отображается только одно изображение, и клиенты потенциально не уверены, покупать продукт или нет.

Теперь разработчик 2 (более опытный) берет дело в свои руки. Он выясняет, какие классы вызывают этот метод, и заставляет вызывающие отправлять флаг, который сообщает getProductImages , какой тип изображений ожидается.

class ProductImageProvider {
    public List getProductImagesByProductId(String productId, String view) {
        //get images where "productId" = productId AND "view" = view
    }
        ...
}

Работает нормально, но как насчет сложности – Чтобы исправить ошибку, которая была на одной странице, разработчик изменил код для всех страниц. Более того, ему потребовалось время, чтобы убедиться, что каждый класс правильно отправил флаг, а затем он снова протестировал каждую функциональность, чтобы убедиться, что не было введено новой ошибки.

Как мы могли бы решить эту проблему?

Решение простое, и оно работает на фундаментальном принципе полиморфизма. Вы ожидаете, что ваш код будет работать по-разному в разных ситуациях. Так почему бы не сделать его более полиморфным?

Наследование

Самый простой способ решить эту проблему – ввести наследование. Мы создаем нового провайдера, который делает что-то особенное для этого нового сценария.

class ProductImageProvider {
    public List getProductImagesByProductId(String productId) {
        //get images where "productId" = productId
    }
        ...
}

class ProductListingImageProvider extends ProductImageProvider {
    public List getProductImagesByProductId(String productId) {
        //get images where "productId" = productId AND "view" = "Listing"
    }
        ...
}

Это прекрасно подходит для одного особого сценария, и именно для этого был создан этот принцип. Но это тоже не идеально. Возможно, в будущем вы добавите аналогичный подкласс для другой страницы. Вскоре некоторые из ваших страниц будут вызывать суперкласс напрямую, а некоторые будут зависеть от специализированных классов. В конечном итоге вам будет трудно понять эти зависимости, и в вашем коде не будет единообразия.

Интерфейсы

Лучшим способом реализовать это было бы скрыть каждую специальную реализацию за интерфейсом.

public interface ProductImageProvider {
    public List getProductImagesByProductId(String productId);
}

class ProductListingImageProvider implements ProductImageProvider {
    public List getProductImagesByProductId(String productId) {
        //get images where "productId" = productId AND "view" = "Listing"
    }
        ...
}

class ProductDisplayImageProvider implements ProductImageProvider {
    public List getProductImagesByProductId(String productId) {
        //get images where "productId" = productId AND "view" = "Product display"
    }
        ...
}

public class ListingPage {
    private ProductImageProvider productImageProvider;
    private String productId;

    public ListingPage(String productId, ProductImageProvider productImageProvider) {
        this.productId = productId;
        this.productImageProvider = productImageProvider;
    }

    public Product getProduct() {
        List images = productImageProvider.getProductImagesByProductId(productId);
    }
}

Теперь каждая страница содержит ссылку на Поставщика изображений продукта. Он получает свои изображения из какой-либо реализации поставщика изображений продукта. Это не связано с логикой инициализации поставщика изображений. Код довольно прост для понимания и согласован для каждой страницы. Мы подробнее поговорим об этом типе поведения интерфейса, когда будем обсуждать Принцип инверсии зависимостей.

Выгоды

  1. Разделение проблем – Вместо того, чтобы использовать один фрагмент кода, решающий все варианты проблемы, теперь у нас есть отдельный код для каждой фундаментальной проблемы. Это делает мой код более удобным в обслуживании и хорошо инкапсулированным.
  2. Обуздать сопутствующий ущерб – Как часто говорят, “Если это работает, не трогайте его”. Когда вы редко касаетесь работающего кода, вы не нарушаете рабочие функции.
  3. Планируйте гибкость – Когда вы пишете свой код с учетом расширяемости, он обеспечивает вам максимальную гибкость в долгосрочной перспективе. Часто в сложных ситуациях было бы быстрее переключиться на другую реализацию, чем изменять фрагмент кода, который вызывается из нескольких мест.

Для вас, чтобы исследовать

Принцип открытого закрытия и полиморфизм являются строительными блоками многих шаблонов проектирования. Некоторые из этих известных шаблонов – Декоратор, Стратегия, Фабрика, Композитный шаблон и шаблон метода. Возможно, вы захотите изучить их, поскольку все они просты для понимания и помогут вам лучше понять OCP.

Оригинал: “https://dev.to/abh1navv/how-solid-is-your-code-open-closed-principle-3k68”