Избыточный код в классе композиции C++

Я пытаюсь подобрать C++. Все шло хорошо, пока моя "тренировочная" программа не затронула меня. Эта загвоздка, я полагаю, проистекает из проблемы дизайна.

Подумайте о Блэкджеке (21). Я сделал несколько уроков.

  1. Карта
  2. колода
  3. Рука
  4. игрок

Колода состоит из - для простоты - имеет множество карт.
-Он может показать все это карты
-Он может перетасовать
-Он может удалить карты

Рука - это колода - с пользой
-Он может рассчитать ценность своей руки
-Он может добавлять карты в руку

Теперь перейдем к моей проблеме - дизайн плеера

У игрока есть рука (личный доступ)
Моя проблема с игроком заключается в том, что в руке есть метод с именем 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Интерфейс может быть добавлен.

Другие вопросы по тегам