Хороший стиль программирования при работе с несколькими объектами

Я программировал версию программного обеспечения настольной игры. До сих пор я писал классы, которые будут соответствовать физическим объектам на игровом поле. Мне хорошо писать логику программы, однако я обнаружил, что многие логические классы требуют доступа к тем же объектам.

Сначала я передавал соответствующие объекты методам, так как они вызывались, но это становилось очень утомительным, особенно когда методы требовали от многих объектов выполнять свои задачи. Чтобы решить эту проблему, я создал класс, который инициализирует и сохраняет все необходимые мне объекты. Это позволяет мне получить доступ к объекту из любого класса, вызвав, например, Assets.dice().

Но теперь, когда я подумал об этом, это не кажется правильным. Вот почему я здесь, боюсь, что создал класс богов. Является ли этот страх необоснованным или я создал рецепт катастрофы?

Ответ 1

В основном вы столкнулись с одноэлементным шаблоном. По большей части это плохая модель. Предоставляя любой части вашего приложения доступ к таким глобальным данным, как это примерно в любое время, вы получаете спагетти-код кода, который трудно поддерживать, отлаживать и, самое главное, тестировать.

Я думаю, что лучше создать "Контекст", содержащий текущие кости, куски и т.д., и передать контекст по мере необходимости методам/классам, которые должны его использовать. Это намного чище, хотя да, это боль, которую нужно пройти по всему миру. Но вы получаете преимущество в том, что вы можете отслеживать, кто имеет доступ к Контексту, когда, а также вы можете создавать mock Contexts для целей тестирования. Если вы передаете контекст в объект высокого уровня, и он должен передать его в свои подкомпоненты, вы знаете, от начала до конца, что такое контекст и откуда он пришел.

Кроме того, в идеале, сделать Context неизменным. Это может быть невозможно. Но если для каждого заданного поворота, если вы можете создать новый Контекст, который фиксирует текущее состояние и неизменен, вы уменьшаете еще больше сюрпризов от вашего приложения.

Ответ 2

Похоже, вы спрашиваете об общей философии объектно-ориентированного программирования. В общем, вы обнаружите, что моделирование объектов реального мира для классов не всегда имеет смысл для вашего кода.

Один из основополагающих принципов, который помог мне понять этот материал, - это диалог между двумя антропоморфными классами (если кто-то знает исходный источник этой цитаты, я был бы признателен за ссылку!):

Класс A говорит классу B: "Дайте мне значение x".

Класс B: "Зачем вам нужно значение x?"

Класс A: "Так что я могу его фланцевать".

Класс B: "Спроси меня, и я сделаю это для тебя".

Это помогает привести домой то, что класс предназначен для инкапсуляции данных и выполнения манипуляций с ним. В общем, это притча, которая помогла мне лучше организовать мой код.

Еще одна вещь, которую вы можете посмотреть, - это некоторые общие объектно-ориентированные Design Patterns. Что-то вроде игрового кубика может иметь большее значение как Singleton, так как вам не нужно больше одного экземпляра.

Если вы хотите получить хорошее представление о Design Patterns, я бы рекомендовал собрать отличную книгу Head First Design Patterns.

Ответ 3

Является ли это действительно "божественным" классом или просто "контекстом", который является "контейнером" для всех связанных между собой экземпляров объектов одной игры, которые затем передаются другим вызовам метода (так что у вас есть только один аргумент)? Последнее довольно распространено, и я не вижу в этом ничего плохого, но в этом случае сам контейнер не имеет реальной функциональности.

Ответ 4

Спасибо, что принесли этот вопрос. Я уже давно задавался вопросом о той же проблеме. Передача объектов по методу и сохранение ограниченных параметров требует создания классов, которые могут содержать все эти объекты.

Тем не менее, я думаю, что дизайн неплох, поскольку вам нужен класс Domain, который проходит через несколько слоев. Если этот класс домена не несет необходимые объекты и не выполняет никакой логики, это должно быть хорошо.

Ответ 5

Наличие такого класса контекста, который имеет доступ ко всему, очень похож на глобальные переменные. Имеются те же недостатки. Глобальную переменную можно читать и изменять любым способом. Это сопрягает все, что использует глобальную переменную друг к другу. Связь плохая, потому что, когда вещи связаны, изменение одного объекта может вызвать что-то в другом объекте. Когда степень сцепления увеличивается, становится очень сложно управлять сложностью (бабочка, хлопающая по крыльям в нашем классе игрока, может вызвать исключение в вашем классе кубиков). Изменение и дополнительное развитие усложняются. Трудно обнаружить и скрывать ошибки становятся неизбежными.

Итак, например, однажды при тестировании вашего приложения вы можете заметить, что ваш объект кости является странным. Вы вызываете dice.roll() и видите, что он возвращает 1 иногда. Но это невозможно в этом случае, потому что ваш игрок перекатывает два из них. Вы отлаживаете и как-то замечаете, что свойство numberOfDice в какой-то момент изменилось на 1. С помощью метода контекста, подобного вашему, нелегко будет найти, кто изменил числоOfDice на 1, потому что каждый имеет доступ к кубикам через ваш объект контекста. Вы поняли?

Итак, каково решение? Одна из метафор, которые мне нравятся для программирования OO, разделяет и побеждает. Вам нужно найти поведение, процессы, объекты, которые могут быть изолированы друг от друга. Вам нужно разделить проблему на управляемые части, которые могут быть изолированы от остальной части материала в вашем приложении.

Изучение того, как это сделать, конечно, нелегко и требует много изучения, чтения, мышления, обсуждения и, конечно, кодирования.