Когда вы впервые изучаете отношения объектов, может быть трудно понять, какой тип отношений должен существовать между объектами и как каждая строка кода и переменная работают вместе, чтобы создать это отношение в программе.
Я покажу вам пошаговый пример того, как абстрактно работают отношения и как работает каждая часть кода, чтобы создать эти отношения в коде, который мы разрабатываем. Я решил использовать мир покемонов, чтобы проиллюстрировать отношения объектов в Ruby, но основную идею можно понять и на других языках.
Взаимоотношения объектов — это концепция, используемая для описания того, как разные классы, создающие объекты, могут взаимодействовать друг с другом с другими созданными нами объектами. Как и в реальной жизни, то, как объекты относятся друг к другу, зависит от отношения каждого объекта друг к другу. Как программисты, мы контролируем, как мы строим эти отношения, которые мы будем называть доменом моделью. Существует несколько основных способов, которыми эти модели соотносятся друг с другом. Я собираюсь углубиться в отношения принадлежит и имеет-многие.
Когда мы думаем о мире покемонов, у нас есть тренер, которым может быть Эш, Брок, Гэри или любой другой тренер покемонов. У индивидуального тренера, скажем, Эша в этом примере, много покемонов: Пикачу, Чаризард, Бульбазавр и многие другие. У каждого из этих отдельных покемонов есть свои движения, например, движения Пикачу могут включать Гром, Удар молнии и Быструю атаку.
В качестве примера я решил сфокусировать нашу доменную модель на популярном Эше Кетчуме, и прямо сейчас у Эша есть следующие три покемона: Пикачу, Чаризард и Бульбазавр. Я также буду использовать слова «экземпляр класса» и «объект» для обозначения одного и того же, поскольку их часто можно использовать взаимозаменяемо.
Ash — это отдельный объект нашего класса Trainer, и объект Ash имеет свой собственный уникальный набор атрибутов, характерных для этого класса. Что я подразумеваю под объектом? Воспринимайте класс Trainer как схему, позволяющую создавать уникальные экземпляры (или копии) класса Trainer, которые мы можем называть объектами, поэтому Ash является экземпляром класса Trainer. У каждого экземпляра класса есть атрибуты, которые делают его уникальным, например, какие атрибуты есть у всех тренеров (кроме покемонов)? Некоторыми общими атрибутами могут быть имя, возможное прозвище, количество покеболов, уровень тренера, тип личности, предметы и количество денег в кармане тренера, и это лишь некоторые из них.
Для простоты я сосредоточусь на нескольких атрибутах Trainer для нашего объекта Ash, который, как вы помните, является одним экземпляром (или объектом) Trainer с уникальными свойствами и атрибутами.
Теперь давайте создадим объект тренера Эша из класса тренера в IRB следующим образом:
Исходя из вышеизложенного, мы можем вернуть нам имя Эша следующим образом:
И мы также можем вернуть уровень объекта Ash следующим образом:
Что касается моделирования нашего объекта Ash, созданного из класса Trainer, в реальном мире, это не очень реалистично. У тренера гораздо больше атрибутов, чем просто имя и уровень. Один из самых важных атрибутов, которого не хватает тренеру покемонов, — это покемон тренера. Как мы видим, наша программа не знает, кто такие покемоны наших объектов Эша, и даже не знает, как вызывать покемонов.
В мире покемонов покемон принадлежит тренеру, а у тренера много покемонов. Как мы можем смоделировать этого покемона, принадлежащего тренеру, с помощью нашего кода? Давайте дадим атрибут покемона отдельному тренеру.
Отношение «принадлежит»
Теперь, когда у нас есть сеттер и геттер для атрибута покемона тренера, давайте снова просмотрим данные в IRB или Pry:
Допустим, мы хотим узнать имя покемона.
Похоже, что «строка», которую мы присвоили этому атрибуту покемона объекта Ash, не имеет доступа к методу name.
Вместо того, чтобы задавать атрибут покемона Эша в виде строки, мы можем установить для него что-то более полезное, например, сложный объект. Мы можем установить его равным объекту Pokémon. Поэтому вместо того, чтобы метод pokemon=() равнялся строке, давайте создадим класс Pokemon и назначим отдельный объект pokemon атрибутам pokemon экземпляра Ash.
Точно так же, как мы смогли установить атрибут pokemon равным строке «Pikachu», мы можем установить атрибут равным экземпляру класса Pokemon, хранящемуся в переменной pikachu.
Теперь мы можем запросить тип.
Теперь отношения между Покемоном и Тренером практически завершены. Мы установили связь «принадлежит». У покемона есть один тренер (в нашей модели предметной области), поэтому мы говорим, что покемон принадлежит тренеру. Мы установили отношения, предоставив объекту Trainer сеттер и геттер для Pokemon.
Если мы посмотрим на блок-схему покемонов, то увидим, что всего три экземпляра покемонов принадлежат объекту Эша. Они связаны горизонтально, потому что все элементы в классе будут иметь одинаковые типы атрибутов, но значения или особенности этих атрибутов будут меняться в зависимости от покемона.
Отношения «имеет много»
Чтобы создать функциональность «имеет много» в нашем классе тренеров и позволить ему иметь много покемонов, нам нужно немного изменить класс.
Мы начали встраивать функциональность в наши классы Trainer, чтобы позволить ему общаться с другими классами и получать много покемонов. Как? Посмотрите на @pokemons = [] в методе инициализации. Что это делает? Каждый раз, когда инициализируется новый класс Trainer (скажем, наш объект Ash), атрибут pokemon представляет собой массив, в котором хранится множество объектов pokemon. Думайте об этом как о месте, где находятся все покемоны Эша. Объектная часть наиболее важна — это не просто цепочка покемонов, а объекты покемонов.
Взгляните на приведенный выше код, и вы увидите, что мы добавили атрибут тренера к классу Pokemon, который, в свою очередь, является сеттером и геттером. Но он не был включен в метод инициализации. Зачем нам это делать? Ответ: кто выбирает, когда добавляются покемоны? Покемон или тренер? Тренер, конечно.
Как показать это с помощью кода? Мы можем перейти к классу Trainer и добавить метод для этого.
Давайте сосредоточимся на методе add_pokemon.
Так в чем здесь аргумент покемонов? Это объект покемонов, созданный из класса покемонов. Мы создали этот метод, который принимает аргумент pokemon, чтобы тренер мог решить, когда добавлять объект Pokémon в свой массив @pokemons. В мире покемонов это будет, когда они поймают покемона.
Хорошо, мы можем посмотреть на следующую строку, которая у нас есть.
@pokemons ‹‹ покемон
Как уже говорилось, pokemon представляет объект pokemon, а @pokemon — это массив, в котором мы решили хранить все объекты pokemon. Как объекты покемонов узнают, с каким игроком у них отношения? Через следующую строку.
У нас есть…
pokemon.trainer = я
Давайте разберем это
pokémon — это аргумент, который мы передали с объектом pokémon.
pokémon.trainer использует установщик и геттер метода train =() для присвоения значения самому себе.
Что такое я?
Self — это объект, получающий вызов метода. Если метод является методом экземпляра, self всегда является экземпляром, с которым мы работаем прямо сейчас. Если метод является методом класса, self будет классом, над которым действует метод.
Возьмем эту информацию о том, что такое «я», вернемся к нашему примеру.
pokemon.trainer = я
Первый вопрос: где мы? Мы находимся в классе Trainer и читаем созданный нами метод, который является методом экземпляра. Мы не хотим, чтобы этот метод работал для класса Trainer в целом, потому что мы хотим убедиться, что конкретный тренер имеет доступ к своему покемону. Кроме того, мы пытаемся добавить покемона к объекту тренера по имени Эш. Следовательно, self относится к объекту Ash.
Мы можем взглянуть на код, протестированный в строке терминала, чтобы увидеть, как работает взаимосвязь в действии.
В строке 1 мы создаем объект Trainer и присваиваем его переменной Ash. Этот объект Ash имеет атрибут имени «Ash» и атрибут уровня тренера «10».
В строке 2 мы создаем объект Pokemon и присваиваем его переменной pikachu. Эта переменная пикачу содержит объект пикачу, который имеет атрибут имени «Пикачу» и атрибут типа «Электрик».
В строке 3 мы вызываем метод add_pokemon. Мы вызываем add_pokemon для конкретного экземпляра тренера, которым является Эш, и передаем объект пикачу в качестве аргумента.
В строке 4 мы проверяем, сработало ли это, и видим, что переменная Ash, содержащая объект Ash, имеет объект покемона внутри массива, который является объектом пикачу;
В строках 5–8 мы решили создать два новых объекта покемонов и назвали их «чаризард» и «бульбазавр», каждый со своими атрибутами. В строках 7 и 8 мы решили добавить каждый из объектов в метод add_pokemon, и это удалось.
В строках 9, 10 мы видим, что у Эша есть три разных объекта покемонов в атрибуте покемонов, который представляет собой массив, а в строке 10 мы видим, что мы можем углубиться, чтобы просто увидеть данные атрибута.
Что дальше?
Хорошо, отлично, похоже, метод работает! Помните, что покемон принадлежит тренеру, а у тренера много покемонов, и эта связь была продемонстрирована. Надеюсь, что эта статья помогла вам лучше понять отношения «многие» и «принадлежит» между двумя объектами в Ruby. В идеале в мире покемонов было бы больше отношений с другими классами, чтобы по-настоящему захватить мир покемонов.
Если вы хотите увидеть приключенческую игру Pokemon CLI, в которой используются эти основы, зайдите сюда, чтобы проверить репозиторий. Некоторые ключевые особенности включают следующее:
- Возможность ловить покемонов и добавлять их в свой список покемонов
- Возможность посмотреть список пойманных покемонов
- Показать сумку
- Посмотреть текущее количество покедолларов
- Магазин — V1 — в будущих версиях планируется добавить полностью функционирующий магазин
- Выйти — сохранить файл сохранения
- Start Battle — V1 — — в будущих версиях планируется добавить полностью функционирующую боевую систему.