oop — избыточный код в классе композиции переполнения стека

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

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

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

Колода состоит из — для простоты — имеет множество карт.

-Это может показать все это карты

-Это может перемешать

-Это может удалить карты

Рука — это колода — с пользой

-Он может рассчитать ценность своей руки

-Это может добавить карты в руку

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

-У игрока есть рука (личный доступ)

Моя проблема с игроком состоит в том, что в руке есть метод с именем addCardToHand. Я чувствую избыточность / плохой дизайн, если мне нужно создать метод Player с именем addCardToHand (Card c), в котором он вызывает и передает один и тот же метод в руки.

или же

объявите Hand h как общедоступный член и в main () сделайте что-то вроде

Игрок р;

Карта aCard;

p.h.addCard (Acard);

Любой совет будет поучительным и высоко ценится. Имейте в виду, я учусь.

4

Решение

Лучший ответ здесь: это зависит 🙂 Хотя я попытаюсь немного уточнить это.

Первый вопрос: имеет ли класс 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;
    }
    }
    

Лично, во втором случае я бы выбрал второе решение — оно достаточно простое и его легко расширять и повторно использовать в случае дальнейших потребностей.

3

Другие решения

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

Вы можете представить некоторые модификации в будущем, например, добавление Playerкеш карт или другие вещи, которые необходимо обновить при вызове пользователя addCardToHand, Куда бы вы добавили код обновления кэша, если бы не реализовали метод пересылки?

Также обратите внимание, что «интерфейс» Player::addCardToHand не должен совпадать с Card::addCard то есть аргументы и возвращаемое значение могут быть разными в этих функциях. Может быть, в этом случае это не так важно, но, как правило, функция пересылки — это место, где происходит перевод Playerинтерфейс и HandИнтерфейс может быть добавлен.

0

По вопросам рекламы [email protected]