Чи можемо ми реально використовувати незмінність в OOP, не втрачаючи всіх основних функцій OOP?


11

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

Це питання стосується тієї ж ідеї, але жодна відповідь не підказує, що є хорошим підходом до незмінності та коли насправді використовувати його. Чи є якісь непогані моделі дизайну? Загальна ідея, здається, "зробить об'єкти непорушними, якщо ви абсолютно не потребуєте їх змін", що марно на практиці.

Мій досвід полягає в тому, що незмінність все більше і більше приводить мій код до функціональної парадигми, і це прогресування завжди відбувається:

  1. Мені потрібні стійкі (у функціональному сенсі) структури даних, такі як списки, карти тощо.
  2. Вкрай незручно працювати з перехресними посиланнями (наприклад, вузол дерева, на який посилаються його діти, тоді як діти посилаються на батьків), що змушує мене взагалі не використовувати перехресні посилання, що знову робить мої структури даних та код більш функціональними.
  3. Спадщина перестає мати сенс, і я замість цього починаю використовувати композицію.
  4. Цілі основні ідеї OOP, як інкапсуляція, починають розпадатися, і мої об'єкти починають виглядати як функції.

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

Просто для зручності я наведу приклад. Будемо мати ChessBoardяк незмінну колекцію незмінних шахових фігур (розширюється абстрактний класPiece). З точки зору ООП, фрагмент відповідає за генерування дійсних рухів зі свого положення на дошці. Але для генерування рухів шматок потребує посилання на свою дошку, тоді як дошка повинна мати посилання на свої шматки. Ну, є кілька хитрощів, щоб створити ці незмінні перехресні посилання залежно від вашої мови ООП, але вони керуються болем, краще не мати деталі для посилання на її дошку. Але тоді шматок не може генерувати свої рухи, оскільки він не знає стан дошки. Потім фрагмент стає просто структурою даних, що утримує тип фрагмента та його положення. Потім можна використовувати поліморфну ​​функцію для генерування рухів для всіх видів творів. Це цілком досяжно у функціональному програмуванні, але майже неможливо в OOP без перевірки типу виконання та інших поганих практик OOP ... Потім,


3
Мені подобається базове питання, але мені важко з деталями. Наприклад, чому спадкування перестає мати сенс, коли ви збільшуєте незмінність?
Мартін Ба

1
У ваших об'єктів немає методів?
Перестаньте шкодити Моніці


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

1
@lishaak Ви "боретеся пояснити проблему із спадщиною простими словами", оскільки це не проблема; поганий дизайн - проблема. Спадщина - це сама суть OOP, необхідна умова, якщо ви хочете. Багато так званих "проблем із успадкуванням" насправді є проблемами з мовами, які не мають явного синтаксису переосмислення, і вони набагато менш проблемні в кращих мовах. Без віртуальних методів та поліморфізму у вас немає ООП; у вас є процедурне програмування з кумедним синтаксисом. Тож не дивно, що ви не бачите переваг ОО, якщо цього уникаєте!
Мейсон Уілер

Відповіді:


24

Чи можемо ми реально використовувати незмінність в OOP, не втрачаючи всіх основних функцій OOP?

Не бачиш, чому ні. Робили це роками, перш ніж Java 8 все-таки стала функціональною. Коли-небудь чули про Струни? Приємний і непорушний з самого початку.

  1. Мені потрібні стійкі (у функціональному сенсі) структури даних, такі як списки, карти тощо.

Потрібні були і ці люди. Недійсні мої ітератори, тому що ви мутували колекцію, коли я читав, це просто грубо.

  1. Вкрай незручно працювати з перехресними посиланнями (наприклад, вузол дерева, на який посилаються його діти, тоді як діти посилаються на батьків), що змушує мене взагалі не використовувати перехресні посилання, що знову робить мої структури даних та код більш функціональними.

Кругові посилання - особливий вид пекла. Незмінюваність вас не врятує від цього.

  1. Спадщина перестає мати сенс, і я замість цього починаю використовувати композицію.

Ну ось я з тобою, але не бачу, що це стосується незмінності. Причина, що мені подобається композиція, не тому, що я люблю динамічну схему стратегії, це тому, що вона дозволяє мені змінити рівень абстракції.

  1. Цілі основні ідеї OOP, як інкапсуляція, починають розпадатися, і мої об'єкти починають виглядати як функції.

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

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

Наразі функціональне програмування модно, і люди скидають деякі помилкові уявлення про ООП. Не дозволяйте цьому заплутати вас у вірі, що це кінець ООП. Функціонал і OOP можуть жити разом досить непогано.

  • Функціональне програмування формально стосується завдань.

  • OOP формально стосується функціональних покажчиків.

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

Дозвольте показати вам щось:

f n (x)

Це функція. Це фактично континуум функцій:

f 1 (x)
f 2 (x)
...
f n (x)

Здогадайтесь, як ми це виражаємо мовами OOP?

n.f(x)

Це мало nщо вибирає, для чого використовується реалізація f, і вона вирішує, які деякі константи використовуються в цій функції (що відверто означає те саме). Наприклад:

f 1 (x) = x + 1
f 2 (x) = x + 2

Це те саме, що передбачають закриття. Якщо закриття посилаються на їх обширну область, об'єктні методи посилаються на стан їхнього примірника. Об'єкти можуть зробити закриття один краще. Закриття - це одна функція, повернута з іншої функції. Конструктор повертає посилання на цілий пакет функцій:

g 1 (x) = x 2 + 1
g 2 (x) = x 2 + 2

Так, ви здогадалися:

n.g(x)

f і g - це функції, які змінюються разом і рухаються разом. Тож ми вклеюємо їх в один і той же мішок. Це те, що є насправді об’єктом. Тримати nпостійне (незмінне) просто означає, що легше передбачити, що вони зроблять, коли ви їх зателефонуєте.

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

Карти відповідальності класу були першими, хто навчив мене думати саме так. Чоловік, я тоді їх збентежив, але чорт, якщо вони досі не актуальні.

Давайте матимемо ChessBoard як незмінну колекцію незмінних шахових фігур (розширюється абстрактний клас Piece). З точки зору ООП, фрагмент відповідає за генерування дійсних рухів зі свого положення на дошці. Але для генерування рухів шматок потребує посилання на свою дошку, тоді як дошка повинна мати посилання на свої шматки.

Аргумент! Знову з непотрібними круговими посиланнями.

Як щодо: A ChessBoardDataStructureперетворює шнури Xy в посилання на фрагменти. Ці шматочки мають метод, який займає x, y і певне, ChessBoardDataStructureі перетворює його на колекцію брендів, що лупцюють нові ChessBoardDataStructures. Потім підштовхує це до чогось, що може вибрати найкращий хід. Тепер ChessBoardDataStructureможе бути незмінним, так само, як і шматки. Таким чином у вас в пам'яті залишається лише один білий пішак. На нього є лише кілька посилань у правильних місцях xy. Об'єктно-орієнтовані, функціональні та незмінні.

Зачекайте, ми вже не говорили про шахи?


6
Усі повинні прочитати це. А потім прочитав на розуміння даних абстракції, Revisited по Вільяму Р. Куку . А потім прочитайте це ще раз. А потім прочитайте Пропозицію Кука щодо спрощених, сучасних визначень "Об'єкт" та "Орієнтований на об'єкт" . Закиньте в « Велику ідею« обмін повідомленнями » Алана Кей , його визначення OO
Йорг W Міттаг

1
І останнє, але не менш важливе, Орландоський договір .
Йорг W Міттаг

1
@Euphoric: Я думаю, що це досить стандартне формулювання, яке відповідає стандартним визначенням для "функціонального програмування", "імперативного програмування", "логічного програмування" тощо. В іншому випадку, C - це функціональна мова, тому що ви можете кодувати FP в ній, логічна мова, тому що ви можете кодувати логічне програмування в ній, динамічна мова, тому що ви можете кодувати динамічне введення в ній, мова актора, тому що ви можете кодувати в ній акторську систему тощо. А Haskell - це найбільша імперативна мова у світі, оскільки ви можете насправді назвати побічні ефекти, зберігати їх у змінних, передавати їх як…
Jörg W Mittag

1
Я думаю, що ми можемо використовувати подібні ідеї до тих, що є у геніальному документі Маттіаса Феллейсена про виразність мови. Переміщення програми OO з, скажімо, Java на C♯ можна здійснити лише з локальними перетвореннями, але переміщення її на C вимагає глобальної перебудови (в основному, вам потрібно ввести механізм відправки повідомлень і перенаправити всі виклики функцій через нього), тому Java і C♯ можуть виражати OO, а C можуть "лише" кодувати його.
Йорг W Міттаг

1
@lishaak Оскільки ви вказуєте це на різні речі. Це тримає його на одному рівні абстракції і запобігає дублюванню позиційного зберігання інформації, що в противному випадку може стати непослідовним. Якщо ви просто обурюєтесь додатковим введенням тексту, тоді вставте його методом, щоб ввести його лише один раз. Тепер у фрагментах зберігається лише інформація, наприклад колір, рухи та зображення / абревіатура, які завжди є правдивими та незмінніми.
candied_orange

2

На мій погляд, найкорисніші концепції, впроваджені OOP в мейнстрім:

  • Правильна модуляція.
  • Інкапсуляція даних.
  • Чіткий поділ між приватним та загальнодоступним інтерфейсами.
  • Чіткі механізми розширення коду.

Усі ці переваги можуть бути реалізовані і без традиційних деталей реалізації, як-от спадкування або навіть класи. Первісна ідея Алана Кей про "об'єктно-орієнтовану систему" використовувала "повідомлення" замість "методів" і була ближчою до Ерланга, ніж, наприклад, C ++. Подивіться на Go, який не відповідає багатьом традиційним деталям впровадження OOP, але все ще відчуває достатньо об'єктну орієнтацію.

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

Погляньте, як Scala намагається поєднати незмінність та підходи FP з OOP. Звичайно, це не найпростіша та елегантна мова. Це досить практично успішно. Також погляньте на Котлін, який пропонує безліч інструментів та підходів для подібної суміші.

Звичайна проблема спробувати інший підхід, ніж творці мови, які мали на увазі N років тому, - це «невідповідність імпедансу» стандартній бібліотеці. OTOH як екосистеми Java, так і .NET в даний час мають розумну підтримку бібліотеки стандартів для змінних структур даних; звичайно, існують і сторонні бібліотеки.

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