Я не знаю ООП

Оригінальна стаття: https://habr.com/ru/post/147927/


Я не вмію програмувати на об'єктно-орієнтованих мовах. Чи не навчився. Після 5 років комерційного програмування на Java я все ще не знаю, як створити хорошу систему в об'єктно-орієнтованому стилі. Просто не розумію.

Я намагався навчитися, чесно. Я вивчав патерни, читав код open source проектів, намагався будувати в голові стрункі концепції, але так і не зрозумів принципи створення якісних об'єктно-орієнтованих програм. Можливо хтось інший їх зрозумів, але не я.

І ось кілька речей, які викликають у мене нерозуміння.

Я не знаю, що таке ООП

Серйозно. Мені складно сформулювати основні ідеї ООП. У функціональному програмуванні однієї з основних ідей є відсутність стану. У структурному - декомпозиція. У модульному - поділ функціоналу в закінчені блоки. У кожній із цих парадигм домінуючі принципи поширюються на 95% коду, а мова спроектований так, щоб заохочувати їх використання. Для ООП я таких правил не знаю.

Прийнято вважати, що об'єктно-орієнтоване програмування будується на 4 основних принципах (коли я був малий, їх було всього 3, але ж тоді і дерева були великими). Ці принципи:

  • абстракція
  • інкапсуляція
  • наслідування
  • поліморфізм

Скидається на звід правил, чи не так? Значить ось воно, ті самі правила, яких потрібно дотримуватися в 95% випадків? Хмм, давайте розглянемо ближче.

Абстракція

Абстракція - це наймогутніший засіб програмування. Саме те, що дозволяє нам будувати великі системи і підтримувати контроль над ними. Навряд чи ми коли-небудь підійшли б хоча б близько до сьогоднішнього рівня програм, якби не були озброєні таким інструментом. Однак як абстракція співвідноситься з ООП?

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

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

Інкапсуляція

Головний козир інкапсуляції в приховуванні реалізації. Клієнтський код бачить тільки інтерфейс, і тільки на нього може розраховувати. Це розв'язує руки розробникам, які можуть вирішити змінити реалізацію. І це дійсно круто. Але питання знову ж в тому, до чого тут ООП? Всі перераховані вище парадигми мають на увазі приховування реалізації. Програмуючи на C ви виділяєте інтерфейс в header-файли, Oberon дозволяє робити поля і методи локальними для модуля, нарешті, абстракція в багатьох мовах будується просто посредствам підпрограм, які також інкапсулюють реалізацію. Більш того, об'єктно-орієнтовані мови самі часто порушують правило інкапсуляції, Надаючи доступ до даних через спеціальні методи - getters і setters в Java, properties в C # і т.д. (У коментарях з'ясували, що деякі об'єкти в мовах програмування не є об'єктами з точки зору ООП: data transfer objects відповідають виключно за перенесення даних, і тому не є повноцінними сутностями ООП, і, отже, для них немає необхідності зберігати инкапсуляцию. З іншого боку , методи доступу краще зберігати для підтримки гнучкості архітектури. Ось так все непросто.) Більш того, деякі об'єктно-орієнтовані мови, такі як Python, взагалі не намагаються щось приховати, а розраховують виключно на розумність азработчіков, що використовують цей код.

Наслідування

Наслідування - це одна з небагатьох нових речей, які дійсно вийшли на сцену завдяки ООП. Ні, об'єктно-орієнтовані мови не створили нову ідею - успадкування цілком можна реалізувати і в будь-який інший парадигмі - однак ООП вперше вивело цю концепцію на рівень самої мови. Очевидні й плюси наслідування: коли вас майже влаштовує якийсь клас, ви можете створити нащадка і перевизначити якусь частину його функціональності. У мовах, що підтримують множинне наслідування, таких як C ++ або Scala (в останній - за рахунок traits), з'являється ще один варіант використання - mixins, невеликі класи, що дозволяють «домішувати» функціональність до нового класу, які не копіюючи код.

Значить, ось воно - те, що виділяє ООП як парадигму серед інших? Хмм ... якщо так, то чому ми так рідко використовуємо його в реальному коді? Пам'ятаєте, я казав про 95% коду, що підкоряються правилам домінуючою парадигми? Я ж не жартував. У Функціональна програмуванні не менш 95% коду використовує незмінні дані і функції без side-ефектів. У модульному практично весь код логічно розфасований по модулях. Преверженци структурного програмування, виконуючи заповіти Дейкстри, намагаються розбивати всі частини програми на невеликі частини. Наслідуванні використовується набагато рідше. Може бути в 10% коду, може бути в 50%, в окремих випадках (наприклад, при наслідуванні від класів фреймворка) - в 70%, але не більше. Тому що в більшості ситуацій це просто не потрібно.

Більш того, наслідування не безпечно для хорошого дизайну. Настільки небезпечно, що Банда Чотирьох (здавалося б, проповідники ООП) в своїй книзі рекомендують при можливості замінювати його на делегування. Спадкування в тому вигляді, в якому воно існує в популярних нині мовами веде до крихкого дизайну. Наслідування від одного предка, клас вже не може наслідуватись від інших. Зміна предка так само стає небезпечним. Існують, звичайно, модифікатори private / protected, але і вони вимагають неслабих екстрасенсорних здібностей для вгадування, як клас може змінитися і як його може використовувати клієнтський код. Наслідування настільки небезпечно і незручно, що великі фреймворки (такі як Spring і EJB в Java) відмовляються від них, переходячи на інші, не об'єктно-орієнтовані засоби (наприклад, метапрограмування). Наслідки настільки непередбачувані, що деякі бібліотеки (такі як Guava) прописуют своїм классам модифікатори, забороняють наслідування, а в новій мові Go було вирішино взагалі відмовитись від наслідвування.

2019-04-03 03:19:48