Як дотримуватися принципу відкритого закриття на практиці


14

Я розумію наміри принципу відкритого закритого типу. Це покликане зменшити ризик зламати щось, що вже працює, змінюючи його, сказавши вам спробувати продовжити, не змінюючи.

Однак у мене виникли проблеми з розумінням того, як цей принцип застосовується на практиці. Наскільки я розумію, це два способи застосувати. Beofore та після можливої ​​зміни:

  1. Перед: програмуйте на абстракції і «прогнозуйте майбутнє» наскільки це можливо. Наприклад, метод drive(Car car)доведеться змінити, якщо Motorcycles в майбутньому буде додано s, тому він, ймовірно, порушує OCP. Але метод drive(MotorVehicle vehicle), швидше за все, доведеться змінити в майбутньому, тому він дотримується OCP.

    Однак передбачити майбутнє і заздалегідь знати, які зміни будуть внесені в систему, досить складно.

  2. Після: коли потрібна зміна, розгорніть клас замість того, щоб змінювати його поточний код.

Практику №1 не важко зрозуміти. Однак у практиці №2 у мене виникають проблеми з розумінням, як подати заявку.

Наприклад (я взяв його з відео на YouTube): припустимо , у нас є метод в класі , який приймає CreditCardоб'єкти: makePayment(CraditCard card). Один день Vouchers додаються в систему. Цей метод не підтримує їх, тому його потрібно модифікувати.

При впровадженні методу ми в першу чергу не спромоглися передбачити майбутнє та програму більш абстрактно (наприклад makePayment(Payment pay), тепер нам доведеться змінити існуючий код.

Практика № 2 говорить, що ми повинні додати функціональність шляхом розширення, а не модифікації. Що це означає? Чи слід підкласити існуючий клас замість того, щоб просто змінити існуючий код? Чи варто робити якусь обгортку навколо неї, щоб уникнути переписування коду?

Або принцип навіть не посилається на "як правильно змінити / додати функціональність", а скоріше посилається на "як уникнути необхідності вносити зміни в першу чергу (тобто програму на абстракції)?



1
Відкритий / закритий принцип не диктує механізм, який ви використовуєте. Спадщина, як правило, неправильний вибір. Також неможливо захистити від усіх майбутніх змін. Найкраще не намагатися передбачити майбутнє, але як тільки потрібна зміна, модифікуйте дизайн, щоб наступні зміни такого типу могли бути прийняті.
Довал

Відповіді:


14

Принципи дизайну завжди повинні бути збалансовані один проти одного. Ви не можете передбачити майбутнє, і більшість програмістів роблять це жахливо, коли вони намагаються. Ось чому у нас є правило трьох , яке в першу чергу стосується дублювання, але стосується і рефакторингу для будь-яких інших принципів дизайну.

Якщо у вас є лише одна реалізація інтерфейсу, вам не потрібно дуже дбати про OCP, якщо тільки не зрозуміло, де відбудуться розширення. Насправді ви часто втрачаєте ясність, намагаючись надмірно проектувати в цій ситуації. Якщо ви продовжите його один раз, ви рефактор зробити його OCP дружнім, якщо це найпростіший і зрозумілий спосіб зробити це. Розширивши його на третю реалізацію, ви переконайтесь, що рефактор його врахує, враховуючи OCP, навіть якщо для цього потрібно трохи більше зусиль.

На практиці, коли у вас є лише дві реалізації, рефакторинг, коли ви додаєте третю, зазвичай не надто складний. Це коли ви дозволяєте йому перерости минулий момент, це стає клопітким у підтримці.


1
Дякую за відповідь. Дозвольте мені побачити, чи я розумію, що ви говорите: те, що ви говорите, що я повинен дбати про OCP, головним чином після того, як мене змусили внести зміни до класу. Значення: при впровадженні заняття в перший раз, я не повинен сильно хвилюватися про OCP, оскільки важко передбачити майбутнє. Коли мені потрібно вперше розширити / змінити його, можливо, це гарна ідея трохи змінити рефакторинг, щоб бути в майбутньому більш гнучким (більше OCP). І в третій раз, коли мені потрібно розширити / змінити клас, настав час зробити деякий рефакторинг, щоб зробити його більш прихильним до OCP. Це ви маєте на увазі?
Авів Кон

1
Така ідея.
Карл Білефельдт

2

Я думаю, ви занадто далеко дивитесь у майбутнє. Вирішіть поточну проблему гнучким способом, який дотримується відкритого / закритого.

Скажімо, вам потрібно реалізувати 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, велосипедом або Jumbo реактивом. Як ви побудуєте єдиний загальний абстрактний тип, який може враховувати всі пристрої, на які люди потрапляють і використовують для мобільності?


Змінення класу, щоб додати ваш новий метод, порушує принцип відкритого закриття. Ваша пропозиція також виключає можливість застосовувати Принцип заміщення Ліскова до всіх транспортних засобів, які можуть керувати, що по суті усуває одну з найсильніших частин ОО.
Данк

@Dunk Свою відповідь я базував на поліморфному принципі відкритого / закритого, а не суворого принципу відкритого / закритого Мейєра. Можливе оновлення класів для підтримки нових інтерфейсів. У цьому прикладі інтерфейс автомобіля зберігається окремо від інтерфейсу мотоцикла. Вони можуть бути формалізовані як окремі абстрактні класи водіння для автомобілів та мотоциклів, які може підтримувати клас реалізації.
Джеффі Томас

@Dunk Принцип заміщення Ліскова корисний, але не приходить безкоштовно. Якщо оригінальна специфікація вимагає лише автомобіля, то створення більш загального транспортного засобу може не коштувати додаткових витрат грошей, часу та складності. Крім того, навряд чи більш загальний транспортний засіб буде ідеально підходить для роботи із незапланованими підкласами. Або інтерфейс для мотоцикла повинен бути вбудований в інтерфейс транспортного засобу (який був призначений тільки для управління автомобілем), або вам потрібно буде модифікувати транспортний засіб для управління мотоциклом (справжнє порушення відкритого / закритого).
Джеффі Томас

Принцип заміни Ліскова не приходить безкоштовно, але він також не вимагає великих витрат. І зазвичай окупається набагато дорожче, ніж це коли-небудь коштує багато разів, навіть якщо інший підклас ніколи не успадковується від нього в основному додатку. Застосування LSP значно спрощує автоматичне тестування, що вже є виграшним. Крім того, хоча ви, звичайно, не повинні дивитися на свиней і припускати, що все буде потрібно LSP, якщо ви створюєте додаток і не маєте гарного відчуття того, що, можливо, знадобиться в майбутньому обороті, тоді ви цього не зробите достатньо знати про вашу програму або її домен.
Данк

1
Що стосується визначення OCP. Можливо, галузі, в яких я працював, вимагають більш високого рівня перевірки, ніж просто звичайна комерційна компанія, але, як правило, якщо файл / клас змінюється, то потрібно не лише перевірити файл / клас, але все, що використовує цей файл / клас у вашому тесті регресії. Тож не має значення, якщо хтось каже, що поліморфний відкритий / закритий є нормальним, зміна інтерфейсу має широкі наслідки, тому це не все так добре.
Данк

2

Я розумію наміри принципу відкритого закритого типу. Це покликане зменшити ризик зламати щось, що вже працює, змінюючи його, сказавши вам спробувати продовжити, не змінюючи.

Йдеться також про те, щоб не порушувати всі об'єкти, які покладаються на цей метод, не змінюючи поведінку вже існуючих об'єктів. Після того, як об’єкт рекламує поведінку, змінювати його буде ризиковано, оскільки ви змінюєте відому та очікувану поведінку об'єкта, не знаючи, що саме інші об'єкти очікують на цю поведінку.

Що це означає? Чи слід підкласити існуючий клас замість того, щоб просто змінити існуючий код?

Так.

"Тільки приймає кредитні картки" визначається як частина поведінки цього класу через його публічний інтерфейс. Програміст заявив світові, що метод цього об'єкта бере лише кредитні картки. Вона зробила це за допомогою не дуже чіткої назви методу, але все зроблено. Решта системи покладаються на це.

Це могло мати сенс у той час, але зараз, якщо це потрібно змінити, ви повинні скласти новий клас, який приймає речі, крім кредитних карток.

Нова поведінка = новий клас

Як осторонь - Хороший спосіб передбачити майбутнє - це подумати над тим, яке ім’я ви дали методу. Ви дали дійсно загальну назву методу звучання, як makePayment, методу з певними правилами методу щодо того, який саме платеж він може здійснити? Це кодовий запах. Якщо у вас є конкретні правила, це слід зрозуміти з назви методу - makePayment має бути makeCreditCardPayment. Зробіть це, коли ви пишете об'єкт вперше, а інші програмісти будуть вам за це вдячні.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.