Як я можу мати об'єкти, які взаємодіють і спілкуються один з одним, не примушуючи ієрархії?


9

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

Зустріньте BoxPong , дуже просту гру, яку я зробив для ознайомлення з об'єктно-орієнтованою розробкою гри. Перетягніть коробку, щоб керувати м'ячем і збирати жовті речі.
Створення BoxPong допомогло мені сформулювати, між іншим, фундаментальне питання: як я можу мати об’єкти, які взаємодіють один з одним, не маючи «належати» один одному? Іншими словами, чи існує спосіб, щоб об’єкти не були ієрархічними, а натомість співіснували? (Я детальніше опишуся нижче).

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

Особливо в таких простих іграх, як BoxPong, зрозуміло, що на одному рівні існує декілька предметів, які співіснують на одному рівні. Там коробка, там кулька, є колекціонування. Все, що я можу виразити об'єктно-орієнтованими мовами, хоча - або так здається - є суворими відносинами HAS-A . Це робиться за допомогою змінних членів. Я не можу просто запустити ballі нехай він робить свою справу, мені потрібно, щоб він постійно належав до іншого об’єкта. Я встановив це так, щоб основний ігровий об’єкт мав ящик, а коробка в свою чергу має м'яч і має лічильник рахунків. Кожен об’єкт також маєupdate()метод, який обчислює положення, напрямок тощо, і я йду схожим шляхом: я називаю метод оновлення основного ігрового об'єкта, який викликає методи оновлення всіх його дітей, а вони, в свою чергу, називають методи оновлення всіх своїх дітей. Це єдиний спосіб я бачу зробити об'єктно-орієнтовану гру, але я вважаю, що це не ідеальний спосіб. Зрештою, я б не вважав, що м'яч належить до коробки, а як те, що він знаходиться на одному рівні та взаємодіє з ним. Я припускаю, що цього можна досягти, перетворивши всі ігрові об’єкти в членські змінні основного ігрового об’єкта, але я не бачу, щоб це вирішило нічого. Я маю на увазі ... залишаючи осторонь очевидний безлад, як би існував спосіб, коли м'яч і коробка пізнавали один одного , тобто взаємодіяли?

Існує також проблема об’єктів, які потребують передачі інформації між собою. У мене є чималий досвід написання коду для SNES, де ви весь час маєте доступ практично до всієї ОЗУ. Скажіть, ви створюєте спеціальний ворог для Super Mario World , і ви хочете, щоб він видалив усі монети Маріо, а потім просто збережіть нуль, щоб отримати $ 0DBF, без проблем. Немає обмежень, які говорять про те, що вороги не можуть отримати статус гравця. Я думаю, що мене зіпсувала ця свобода, тому що за допомогою C ++ і подібних мені часто стає цікаво, як зробити цінність доступною для інших об'єктів (або навіть глобальних).
Використовуючи приклад BoxPong, що робити, якщо я хотів, щоб м'яч відскочив від країв екрана? widthі heightє властивостями Gameкласу,ballмати доступ до них. Я міг би передавати такі значення (або через конструктори, або через методи, де вони потрібні), але це просто кричить на мене погану практику.

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

Я чув про "класи друзів" на C ++ і свого роду знаю, як вони працюють, але якщо вони є кінцевим рішенням, то як я не бачу friendключових слів, розлитих у кожному проекті C ++, і як з'являються Концепція не існує в кожній мові ООП? (Те саме стосується функціональних покажчиків, про які я нещодавно дізнався.)

Заздалегідь дякую за будь-які відповіді - і знову, якщо є частина, яка не має для вас сенсу, повідомте мене.


2
Значна частина ігрової індустрії перейшла до архітектури Entity-Component-System та її варіацій. Це інший спосіб мислення від традиційних підходів до ОО, але він працює добре і має сенс, як тільки концепція зануриться. Єдність використовує це. Насправді Unity просто використовує частину Entity-Component, але базується на ECS.
Данк

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

Відповіді:


13

Взагалі, це виходить дуже погано, якщо об’єкти одного рівня знають один про одного. Як тільки об’єкти знають один про одного, вони прив’язуються або з’єднуються один з одним. Це робить їх важкими для зміни, важко перевірити, важко підтримувати.

Це виходить набагато краще, якщо є якийсь об’єкт «нагорі», який знає про двох і може встановлювати взаємодії між ними. Об'єкт, який знає про двох однолітків, може зв'язати їх разом через ін'єкцію залежності або через події або через передачу повідомлення (або будь-який інший механізм роз'єднання). Так, це призводить до штучної ієрархії, але це набагато краще, ніж бардак із спагетті, який ви отримуєте, коли речі просто взаємодіють вольово-невольно. Це лише важливіше для C ++, оскільки вам потрібно щось, щоб також мати час життя об’єктів.

Отже, коротше кажучи, ви можете це зробити, просто об'єкти поруч скрізь пов’язані між собою спеціальним доступом, але це погана ідея. Ієрархія забезпечує порядок і чітку власність. Головне пам’ятати, що об’єкти в коді не обов’язково є предметами в реальному житті (або навіть грі). Якщо об’єкти в грі не мають хорошої ієрархії, може бути кращою інша абстракція.


2

Використовуючи приклад BoxPong, що робити, якщо я хотів, щоб м'яч відскочив від країв екрана? ширина та висота - це властивості класу Ігри, і мені знадобиться м'яч, щоб мати доступ до них.

Ні!

Я думаю, що головне питання у вас виникає - це ви сприймаєте "Об'єктно-орієнтоване програмування" занадто буквально. В ООП об'єкт не представляє "річ", а "ідею", що означає, що "М'яч", "Гра", "Фізика", "Математика", "Дата" і т. Д. Усі є дійсними об'єктами. Немає також вимоги, щоб об’єкти ні про що «знали». Наприклад, Date.Now().getTommorrow()запитайте у комп’ютера, який сьогодні день, застосуйте правила таємної дати, щоб визначити дату завтра, і повернути це абоненту. DateОб'єкт не знає ні про що ще, це потрібно тільки запитує інформацію по мірі необхідності з системи. Крім того, Math.SquareRoot(number)не потрібно нічого знати, окрім логіки, як обчислити квадратний корінь.

Тож у вашому прикладі я цитував, що «Бал» не повинен нічого знати про «Коробку». Коробки і кульки - абсолютно різні ідеї, і не мають права спілкуватися один з одним. Але двигун фізики знає, що таке Box and Ball (або принаймні, ThreeDShape), і він знає, де вони знаходяться і що з ними має відбуватися. Тож якщо м'яч скорочується через те, що він холодний, фізичний двигун скаже, що кульовий екземпляр зараз менший.

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

І у вашому прикладі Маріо, що, якщо ви знаходитесь у приміщенні, де доторкатися до ворога, не зливає монети Маріоса, а просто викидає його з цієї кімнати? За межами простору ідеї Маріо або противника Маріо повинен втрачати монети, торкаючись ворога (адже, якщо в Маріо є зірка невразливості, він замість цього вбиває ворога). Отже, будь-який об'єкт (домен / ідея), який відповідає за те, що відбувається, коли Маріо торкається ворога, - це єдиний, про який потрібно знати, і повинен робити все, що завгодно з будь-яким із них (настільки, наскільки цей об'єкт дозволяє змінювати зовнішні зміни ).

Крім того, з вашими твердженнями про об'єкти, що викликають дітей Update(), це надзвичайно схильна помилка, як, що, якби Updateвикликається декілька разів за кадр від різних батьків? (навіть якщо ви це зробите, це витрачений час процесора, який може сповільнити вашу гру) Кожен повинен торкатися лише того, що їм потрібно, коли їм потрібно. Якщо ви використовуєте Update (), вам слід використовувати певну форму шаблону підписки, щоб переконатися, що всі оновлення викликаються один раз на кадр (якщо це не обробляється для вас, як в Unity)

Навчання, як визначати свої доменні ідеї в чіткі, ізольовані, чітко визначені та прості у використанні блоки, буде найбільшим фактором того, наскільки добре ви можете використовувати OOP.


1

Зустріньте BoxPong, дуже просту гру, яку я зробив для ознайомлення з об'єктно-орієнтованою розробкою гри.

Створення BoxPong допомогло мені сформулювати, між іншим, фундаментальне питання: як я можу мати об’єкти, які взаємодіють один з одним, не маючи «належати» один одному?

У мене є чималий досвід написання коду для SNES, де ви весь час маєте доступ практично до всієї ОЗУ. Скажіть, ви створюєте спеціальний ворог для Super Mario World, і ви хочете, щоб він видалив усі монети Маріо, а потім просто збережіть нуль, щоб отримати $ 0DBF, без проблем.

Здається, вам не вистачає точки об’єктно-орієнтованого програмування.

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

Що таке залежність? Залежність - це опора на щось інше. Коли ви зберігаєте нуль для адреси $ 0DBF, ви покладаєтесь на те, що ця адреса знаходиться там, де розташовані монети Маріо і що монети представлені як ціле число. Ваш власний код ворога залежить від коду, який реалізує Маріо та його монети. Якщо ви внесете зміни до місця, де Маріо зберігає свої монети в пам'яті, потрібно вручну оновити весь код, на який посилається місце пам'яті.

Об'єктно-орієнтований код - це те, щоб ваш код залежав від абстракцій, а не від деталей. Тож замість

class Mario
{
    public:
        int coins;
}

ти б написав

class Mario
{
    public:
        void LoseCoins();

    private:
        int coins;
}

Тепер, якщо ви хочете змінити, як Маріо зберігає свої монети від int до long або double, або зберігає їх у мережі або зберігає в базі даних або розпочинає якийсь інший тривалий процес, ви вносите зміни в одному місці: Клас Маріо, і всі ваші інші коди продовжують працювати без змін.

Тому, коли ви запитаєте

як я можу мати об’єкти, які взаємодіють один з одним, не маючи приналежності один до одного?

ви справді запитуєте:

як я можу мати код, який безпосередньо залежить один від одного без будь-яких абстракцій?

що не є об’єктно-орієнтованим програмуванням.

Я пропоную вам почати з того, щоб прочитати все тут: http://objectmentor.com/omSolutions/oops_what.html, а потім шукати на YouTube на всьому Роберт Мартін і переглянути все це.

Мої відповіді випливають з нього, і деякі з них прямо цитуються від нього.


Дякуємо за відповідь (і сторінка, на яку ви пов’язали; виглядає цікаво). Я фактично знаю про абстракцію та повторне використання, але, мабуть, я не дуже добре поставив це у своїй відповіді. Однак із наведеного вами коду прикладу я можу краще проілюструвати свою думку зараз! Ви в основному говорите, що ворожий об’єкт не повинен робити mario.coins = 0;, але mario.loseCoins();це добре і правда - але мій погляд, як ворог може отримати доступ до marioоб'єкта в будь-якому випадку? marioбути змінною члена enemyне здається мені правильним.
вві

Ну і проста відповідь - передати Маріо як аргумент функції в Enemy. У вас може бути така функція, як marioNearby () або attackMario (), яка б взяла Маріо як аргумент. Тож тоді, коли запускається логіка, коли Враг та Маріо повинні взаємодіяти, ви б викликали ворота.marioNearby (mario), який би викликав mario.loseCoins (); Пізніше по дорозі ви можете вирішити, що існує клас ворогів, які змушують Маріо втратити лише одну монету або навіть отримати монети. Тепер у вас є одне місце, щоб здійснити таку зміну, яка не спричиняє побічних змін до іншого коду.
Марк Мерфін

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

@Tezra Але чи не є ці об'єкти вищого порядку взагалі багаторазовими? Складається враження, що ці об'єкти виконують функції, вони існують лише як процедура, яку вони демонструють.
Стів Чамайлард

@SteveChamaillard У кожній програмі буде принаймні трохи специфічної логіки, яка не має сенсу в будь-якій іншій програмі, але ідея полягає в тому, щоб цю логіку було виділено до кількох класів високого порядку. Якщо у вас є клас маріо, ворог і рівень, ви можете використовувати маріо і ворога в інших іграх. Якщо ви прив’язуєте ворога та маріо безпосередньо один до одного, то будь-яка гра, яка потребує однієї, повинна також тягнути до іншої.
Тезра

0

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

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

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

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

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