OCP (Open/Closed Principle) — другий принцип SOLID, який визначає: програмні сутності повинні бути відкриті для розширення, але закриті для модифікації. Цей принцип, сформульований Бертраном Мейєром у 1988 році, дозволяє додавати нову функціональність без зміни існуючого коду. Згідно з книгою Роберта Мартіна Clean Architecture (2017), принцип відкритості реалізується через абстракції та поліморфізм, мінімізуючи ризик регресійних помилок.
Головне
OCP (Open/Closed Principle) — принцип відкритості для розширення та закритості для модифікації. Класи, модулі та функції повинні бути спроєктовані так, щоб нову поведінку можна було додавати без зміни їхнього вихідного коду. Розширення досягається через наслідування, композицію або підстановку реалізацій інтерфейсів.
Бертран Мейєр у книзі Object-Oriented Software Construction (1988) вперше описав OCP через наслідування: базовий клас залишається незмінним, а підкласи розширюють його поведінку. Сучасне трактування OCP, запропоноване Робертом Мартіном, спирається на поліморфізм та інтерфейси: замість наслідування використовуються абстрактні контракти.
Різниця підходів суттєва. Наслідування створює жорсткий зв’язок між базовим та похідними класами. Інтерфейси та композиція дають гнучкість: реалізацію можна замінити без зміни клієнтського коду. Сучасний OCP — це про абстракцію, а не про наслідування.
Поліморфний OCP використовує абстрактні класи або інтерфейси для визначення контракту. Клієнтський код працює з абстракцією, не знаючи конкретної реалізації. Нова функціональність додається створенням нового класу, що реалізує той самий інтерфейс, — без жодної зміни існуючого коду. Це робить систему стійкою до змін та передбачуваною для розширення.
У мобільній розробці цей підхід повсюдний: патерн Strategy дозволяє підміняти алгоритми (стиснення зображень, кешування, автентифікація) через єдиний інтерфейс. Додавання нової стратегії не потребує зміни коду, який її використовує.
Реалізація OCP починається з виділення змінної поведінки в абстракцію. Якщо в коді є конструкція switch або ланцюжок if-else, що перевіряє тип об’єкта, — це сигнал до застосування OCP. Кожна гілка умови потенційно потребує додавання нової гілки при розширенні.
Процес рефакторингу під OCP включає три кроки: визначити змінний аспект (те, що може розширюватися), виділити його в інтерфейс або абстрактний клас, переписати клієнтський код на роботу з абстракцією замість конкретного класу. Після цього нова функціональність додається без зміни клієнта.
Важливе уточнення: закритість для модифікації не абсолютна. Якщо зміна вимоги зачіпає саму абстракцію або контракт — зміна неминуча. OCP захищає від змін у реалізаціях, а не в контрактах. Хороший дизайн передбачає, що контракти стабільні, а реалізації варіативні.
При оцінці OCP-сумісності архітектури корисно дивитися на точки розширення. Кожна точка, де розробник додає if-else або switch для нового типу, — претендент на абстракцію. Система, спроєктована за OCP, має передбачувані точки розширення: інтерфейси з документацією «реалізуй цей інтерфейс, щоб додати новий тип». В Android такий приклад — патерн Factory у парі з ViewModelProvider.Factory: додавання нового типу ViewModel не потребує зміни існуючих фабрик.
Найефективніші шаблони для дотримання OCP у мобільній розробці включають Strategy, Template Method, Decorator та Factory. Кожен з них вирішує задачу розширення поведінки без модифікації існуючого коду через різні механізми об’єктно-орієнтованого проєктування.
Strategy дозволяє підміняти алгоритми на льоту через спільний інтерфейс. У iOS-розробці стратегії використовуються для анімацій та валідації форм. Template Method визначає скелет алгоритму в базовому класі, а підкласи перевизначають кроки — підходить для екранів зі спільною структурою, але різним наповненням.
Decorator динамічно додає поведінку об’єкту без зміни його класу. В Android Decorator застосовується для обгортання Repository шаром кешу або логування. Factory Method створює об’єкти через інтерфейс, дозволяючи підкласам вирішувати, який клас інстанціювати, — основа OCP-сумісного створення залежностей.
Вибір шаблону залежить від стабільності розширюваної поведінки. Strategy оптимальна, коли алгоритми замінюються цілком. Template Method — коли структура фіксована, але кроки варіативні. Decorator — коли розширення має бути прозорим для клієнта. Для більшості сценаріїв в Android та iOS достатньо Strategy + впровадження залежностей.
Застосування цих шаблонів без OCP технічно можливе, але втрачає сенс. Саме OCP обґрунтовує, навіщо ми вводимо додатковий рівень абстракції: щоб система зростала без переписування існуючого коду.
Розглянемо Android-приклад з обробкою платежів. Без OCP кожна нова платіжна система потребує зміни класу-обробника. З OCP додається нова реалізація інтерфейсу без зміни існуючого коду.
// Порушення OCP: switch потребує зміни при додаванні нової системи
class BadPaymentProcessor {
fun process(type: String) {
when (type) {
"card" -> // обробка карти
"paypal" -> // обробка PayPal
}
}
}
// OCP-сумісний дизайн
interface PaymentMethod {
fun pay(amount: Double)
}
class CardPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
class PayPalPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
// Нова система — новий клас, без зміни існуючого коду
class ApplePayPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
iOS-приклад з валідацією текстових полів демонструє ту саму логіку через протоколи Swift:
// OCP-сумісна валідація
protocol ValidationRule {
func validate(_ input: String) -> Bool
}
struct EmailRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.contains("@")
}
}
struct PhoneRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.count == 11
}
}
// Додавання нового правила не потребує зміни коду валідатора
struct PasswordRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.count >= 8
}
}
Ключова перевага OCP у цих прикладах: додавання ApplePay або PasswordRule не потребує зміни існуючих класів. Код розширюється горизонтально — через нові файли, а не через зміну старих. Це знижує ризик регресії та прискорює впровадження нового функціоналу.
Найпоширеніше порушення — конструкція switch або when за типом об’єкта. Щоразу при додаванні нового типу доводиться знаходити всі такі switch у коді та додавати нову гілку. Пропущений switch — баг у рантаймі, який складно виявити на етапі компіляції.
У мобільній розробці OCP порушується при використанні гігантських enum-класів з методами, що залежать від значення enum. Додавання нового елемента enum потребує зміни кожного switch по всьому проєкту. Альтернатива — поліморфізм через інтерфейс, де кожен тип реалізує свою поведінку.
Ще одне типове порушення — God Adapter: RecyclerView.Adapter (Android) або UITableViewDataSource (iOS), який через if-else обробляє різні типи комірок. Кожен новий тип комірки потребує розширення адаптера. Рішення — поліморфний ViewHolder зі спільним методом bind, де кожен тип комірки відповідає за своє відображення.
Превентивні заходи включають: відмову від switch за типом на користь поліморфізму, впровадження залежностей через інтерфейси та застосування патерну Factory для створення об’єктів за конфігурацією. Аналіз коду на наявність «перемикачів за типом» — обов’язкова частина код-рев’ю в OCP-орієнтованих командах.
Рефакторинг існуючого порушення OCP виконується через Replace Conditional with Polymorphism: кожна гілка умови стає окремим класом з імплементацією спільного інтерфейсу. Клієнтський код переписується на роботу з інтерфейсом, а конкретна реалізація підставляється через фабрику або DI-контейнер.
Важливо розуміти, що OCP та поліморфізм не вирішують усіх проблем розширення. Якщо архітектура обрана невірно, додавання нової функціональності потребуватиме зміни не лише реалізацій, але й контрактів. Хороша архітектура передбачає напрямки розширення та закладає абстракції саме в цих точках. Інвестиції в OCP окупаються тим більше, чим довше живе проєкт і чим частіше змінюються вимоги до конкретних модулів.
Часті запитання
Ні. OCP забороняє зміну існуючого коду при додаванні нової функціональності, що належить до тієї самої абстракції. Зміна контракту, виправлення багів та рефакторинг не є порушенням OCP — принцип захищає від каскадних правок при розширенні.
Strategy — пряма реалізація OCP. Інтерфейс стратегії визначає контракт, клієнт залежить від абстракції, а конкретні стратегії реалізують варіативну поведінку. Додавання нової стратегії не потребує зміни клієнта — це і є відкритість для розширення при закритості для модифікації.
Так, через наслідування та Template Method: базовий клас визначає скелет алгоритму, підкласи перевизначають кроки. Однак наслідування створює жорсткий зв’язок і менш гнучке, ніж інтерфейси. У сучасній розробці інтерфейси та композиція вважаються кращим способом реалізації OCP.
OCP-сумісний код спрощує тестування: кожна реалізація інтерфейсу тестується ізольовано. Клієнтський код тестується з мок-реалізацією, що дозволяє перевіряти логіку без прив’язки до конкретної поведінки. Розширення системи не потребує переписування існуючих тестів.
Ні. OCP виправданий, коли розширення функціональності передбачуване. Для стабільного коду, який не планується розширювати, додаткова абстракція надлишкова. YAGNI (You Ain't Gonna Need It) — хороший противаг OCP: абстракцію вводять, коли з’являється другий варіант поведінки, а не на випередження.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також