Я понимаю цель принципа открытого-закрытого. Он предназначен для того, чтобы уменьшить риск поломки чего-либо, что уже работает при его изменении, предлагая вам попытаться расширить без изменения.
Однако у меня возникли проблемы с пониманием того, как этот принцип применяется на практике. Насколько я понимаю, есть два способа его применения. До и после возможного изменения:
До: программируйте на абстракции и «предсказывайте будущее» столько, сколько сможете. Например, метод
drive(Car car)
должен будет измениться, еслиMotorcycle
в будущем в систему будут добавлены s, поэтому он, вероятно, нарушает OCP. Ноdrive(MotorVehicle vehicle)
в будущем этот метод вряд ли изменится, поэтому он придерживается OCP.Тем не менее, довольно сложно предсказать будущее и заранее знать, какие изменения будут внесены в систему.
После: когда требуется изменение, расширяйте класс вместо изменения его текущего кода.
Практику № 1 не сложно понять. Однако на практике № 2 у меня возникают проблемы с пониманием того, как подать заявку.
Например (я взял его из видео на YouTube): допустим , у нас есть метод в классе , который принимает CreditCard
объекты: makePayment(CraditCard card)
. Один день Voucher
добавляются в систему. Этот метод не поддерживает их, поэтому его необходимо изменить.
При реализации метода в первую очередь нам не удалось предсказать будущее и программировать в более абстрактных терминах (например makePayment(Payment pay)
, теперь мы должны изменить существующий код.
Практика № 2 говорит, что мы должны добавить функциональность, расширяя, а не модифицируя. Что это обозначает? Должен ли я создать подкласс существующего класса вместо простого изменения его существующего кода? Должен ли я сделать вокруг него какую-то обертку, чтобы избежать переписывания кода?
Или принцип даже не относится к «как правильно изменить / добавить функциональность», а скорее относится к «как избежать необходимости вносить изменения в первую очередь (т.е. программировать абстракции)?
Ответы:
Принципы дизайна всегда должны быть сбалансированы друг с другом. Вы не можете предсказать будущее, и большинство программистов делают это ужасно, когда пытаются. Вот почему у нас есть правило трех , которое в первую очередь касается дублирования, но также применяется к рефакторингу для любых других принципов проектирования.
Когда у вас есть только одна реализация интерфейса, вам не нужно сильно заботиться об OCP, если только не ясно, где будут иметь место какие-либо расширения. Фактически, вы часто теряете ясность, пытаясь чрезмерно проектировать в этой ситуации. Когда вы расширяете его один раз, вы делаете рефакторинг, чтобы сделать его OCP-дружественным, если это самый простой и понятный способ сделать это. Когда вы расширяете его до третьей реализации, вы обязательно реорганизуете его с учетом OCP, даже если это требует немного больше усилий.
На практике, когда у вас есть только две реализации, рефакторинг при добавлении третьей обычно не слишком сложен. Когда вы позволяете ему расти выше этой точки, становится трудно поддерживать его.
источник
Я думаю, что вы смотрите слишком далеко в будущее. Решите текущую проблему гибким способом, который придерживается, чтобы открыть / закрыть.
Допустим, вам нужно реализовать
drive(Car car)
метод. В зависимости от вашего языка у вас есть несколько вариантов.Для языков, которые поддерживают перегрузку (C ++), просто используйте
drive(const Car& car)
В какой-то момент позже вам может понадобиться
drive(const Motorcycle& motorcycle)
, но это не помешаетdrive(const Car& car)
. Нет проблем!Для языков, которые не поддерживают перегрузку (Цель C), включите имя типа в метод
-driveCar:(Car *)car
.В какой-то момент позже вам может понадобиться
-driveMotorcycle:(Motorcycle *)motorcycle
, но опять же, это не помешает.Это позволяет
drive(Car car)
быть закрытым для модификации, но открыто для распространения на другие типы транспортных средств. Это минималистическое будущее планирование, которое позволяет вам выполнять работу сегодня, но не дает вам блокировать себя в будущем.Попытка представить самые основные типы, которые вам нужны, может привести к бесконечному регрессу. Что происходит, когда вы хотите управлять Segue, велосипедом или джамбо-джетом. Как создать единый общий абстрактный тип, который может учитывать все устройства, которые люди используют и используют для мобильности?
источник
Речь также идет о том, чтобы не разрушать все объекты, которые зависят от этого метода, не изменяя поведение уже существующих объектов. Как только объект объявил об изменении поведения, это рискованно, поскольку вы изменяете известное и ожидаемое поведение объекта, не зная точно, что другие объекты ожидают такого поведения.
Ага.
«Только принимает кредитные карты» определяется как часть поведения этого класса через его открытый интерфейс. Программист объявил миру, что метод этого объекта принимает только кредитные карты. Она сделала это, используя не совсем понятное имя метода, но оно сделано. Остальная часть системы полагается на это.
В то время это могло иметь смысл, но теперь, если это нужно изменить сейчас, вы должны создать новый класс, который принимает вещи, отличные от кредитных карт.
Новое поведение = новый класс
В качестве отступления . Хороший способ предсказать будущее - подумать о названии, которое вы дали методу. Вы дали действительно общее название метода, такого как makePayment, методу с конкретными правилами в методе относительно того, какой именно платеж он может сделать? Это запах кода. Если у вас есть определенные правила, это должно быть ясно из названия метода - makePayment должна быть makeCreditCardPayment. Делайте это, когда вы пишете объект в первый раз, и другие программисты будут вам благодарны за это.
источник