Избыточный код в классе композиции C++
Я пытаюсь подобрать C++. Все шло хорошо, пока моя "тренировочная" программа не затронула меня. Эта загвоздка, я полагаю, проистекает из проблемы дизайна.
Подумайте о Блэкджеке (21). Я сделал несколько уроков.
- Карта
- колода
- Рука
- игрок
Колода состоит из - для простоты - имеет множество карт.
-Он может показать все это карты
-Он может перетасовать
-Он может удалить карты
Рука - это колода - с пользой
-Он может рассчитать ценность своей руки
-Он может добавлять карты в руку
Теперь перейдем к моей проблеме - дизайн плеера
У игрока есть рука (личный доступ)
Моя проблема с игроком заключается в том, что в руке есть метод с именем addCardToHand. Я чувствую избыточность / плохой дизайн, если мне нужно создать метод Player с именем addCardToHand(Card c), в котором он вызывает и передает один и тот же метод в руке.
или же
объявите Hand h как общедоступный член и в main() сделайте что-то вроде
Игрок р;
Карта aCard;
phaddCard(Acard);
Любой совет будет поучительным и высоко ценится. Имейте в виду, я учусь.
2 ответа
Лучший ответ здесь: это зависит:) Я постараюсь немного уточнить это, хотя.
Первый вопрос: имеет ли класс Player какую-либо внутреннюю логику? Если это простой контейнер для Hand, я бы просто написал Player.GetHand().AddCard()
потому что нет причин дублировать код внутри Player.AddCard()
метод, и проблема решена.
Предположим теперь, что существует необходимость в реализации дополнительной логики для добавления карты в руку Игрока. Это означает, что дополнительный код в классе Player должен вызываться при добавлении карты в руку. В таком случае я вижу три возможных решения.
(Источники только для демонстрационных целей, могут не компилироваться)
Ограничьте доступ к Hand, чтобы никто не мог получить его из Player. Плеер должен будет реализовать такие методы, как AddToHand, RemoveFromHand и т. Д. Выполнимо, но неудобно в использовании.
class Player { private: Hand hand; public: void AddToHand(Card & card) { hand.Add(card); } };
Используйте шаблон наблюдателя. Когда пользователь (пользователь класса) вызывает Player.GetHand().AddCard(), Рука уведомляет Игрока, что данные изменились, и Игрок может действовать соответственно. Вы можете легко добиться этого, используя std::function из C++11 для реализации событий.
class Deck { private: std::function<void(void)> cardsChanged; public: void Add(Card card) { // Add a card if (!(cardsChanged._Empty())) cardsChanged(); } void SetCardsChangedHandler(std::function<void(void)> newHandler) { cardsChanged = newHandler; } }; // (...) class Player { private: Hand hand; void CardsChanged() { ... } (...) public: Player() { hand.SetCardsChangedHandler([&this]() { this.CardsChanged(); } ); } };
Определите интерфейс IHand со всеми необходимыми методами интерфейса. Очевидно, что Hand должен реализовывать IHand, а Player.GetHand () должен возвращать IHand. Хитрость в том, что IHand, возвращаемый Player, не обязательно должен быть экземпляром Hand, но вместо этого он может быть декоратором, действующим как мост между пользователем и реальным экземпляром Hand (см. Образец декоратора).
class IHand { public: virtual void Add(Card card) = 0; virtual void Remove(Card card) = 0; }; class Hand : public IHand { // Implementations } class PlayersHand : public IHand { private: Hand & hand; Player & player; public: PlayersHand(Hand & newHand, Player & newPlayer) { hand = newHand; player = newPlayer; } void Add(Card card) { hand.Add(card); player.HandChanged(); } // ... }; class Player { private: Hand hand; PlayersHand * playersHand; public: Player() { playersHand = new PlayersHand(hand, this); } IHand GetHand() { return playersHand; } }
Лично, во втором случае я бы выбрал второе решение - оно достаточно простое и его легко расширять и повторно использовать в случае дальнейших потребностей.
Функция переадресации вызовов является обычной практикой. Вы должны думать об этом как о добавлении некоторого уровня абстракции. Это опять не то же самое (что означало бы избыточность), а реализация одного метода с использованием другого.
Вы можете представить некоторые модификации в будущем, например, добавление Player
кеш карт или другие вещи, которые необходимо обновить при вызове пользователя addCardToHand
, Куда бы вы добавили код обновления кэша, если бы не реализовали метод пересылки?
Также обратите внимание, что "интерфейс" Player::addCardToHand
не должен совпадать с Card::addCard
т.е. аргументы и возвращаемое значение могут быть разными в этих функциях. Может быть, в этом случае это не так важно, но, как правило, функция пересылки - это место, где происходит перевод Player
интерфейс и Hand
Интерфейс может быть добавлен.