Javanotes 8. 1, Раздел 5. 4 - Пример программирования: карта, рука, колода

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

В этом разделе мы рассмотрим некоторые конкретные примеры объектно-ориентированного проектирования в области, которая достаточно проста, чтобы у нас был шанс придумать что-то разумно пригодное для повторного использования. Рассмотрим карточные игры, в которые играют стандартной колодой игральных карт (так называемая «покерная» колода, поскольку она используется в игре в покер).

5.4.1 Проектирование классов

В типичной карточной игре каждый игрок получает по раздаче карт. Колода перемешивается, и карты сдаются из колоды по одной и складываются в руки игроков. В некоторых играх карты можно убирать из руки и добавлять новые. Игра выиграна или проиграна в зависимости от достоинства (туз, 2, 3,. Король) и масти (пики, бубны, трефы, червы) карт, которые получает игрок. Если мы посмотрим на существительные в этом описании, то мы увидим несколько кандидатов на объекты: игра, игрок, рука, карта, колода, значение и масть. Из них значение и масть карты являются простыми значениями, и они могут быть просто представлены как переменные экземпляра в объекте Card. В полной программе остальные пять существительных могут быть представлены классами. Но давайте поработаем над теми, которые, очевидно, являются многоразовыми: карта, рука., и колода.

Если мы посмотрим на глаголы в описании карточной игры, мы увидим, что мы можем перетасоватьколоду и раздатькарту из колоды. Это дает нам возможность использовать два кандидата в методы экземпляра в классе Deck: shuffle () и dealCard (). Карты можно добавлять и снимать с рук. Это дает два кандидата в методы экземпляра в классе Hand: addCard () и removeCard (). Карты - относительно пассивные вещи, но нам, по крайней мере, нужно уметь определять их масти и значения. По мере продвижения мы откроем для себя больше методов экземпляра.

Сначала мы подробно разработаем класс колоды. Когда колода карт создается впервые, она содержит 52 карты в некотором стандартном порядке. Классу Deck потребуется конструктор для создания новой колоды. Конструктору не нужны параметры, потому что любая новая колода такая же, как и любая другая. Будет метод экземпляра, называемый shuffle (), который переставит 52 карты в случайном порядке. Метод экземпляра dealCard () получит следующую карту из колоды. Это будет функция с возвращаемым типом Card, поскольку вызывающий должен знать, какая карта сдается. У него нет параметров - когда вы сдаете следующую карту из колоды, вы не передаете колоду никакой информации; вы просто получаете следующую карту, какой бы она ни была.Что произойдет, если в колоде больше не останется карт при вызове ее метода dealCard ()? Вероятно, следует считать ошибкой попытку раздать карту из пустой колоды, поэтому в этом случае колода может вызвать исключение. Но здесь возникает другой вопрос: как остальная часть программы узнает, пуста ли колода? Конечно, программа может отслеживать, сколько карточек она использовала. Но сама колода должна знать, сколько карт в ней осталось, поэтому программа должна просто иметь возможность запрашивать объект колоды. Мы можем сделать это возможным, указав другой метод экземпляра, cardsLeft (), который возвращает количество карт, оставшихся в колоде. Это приводит к полной спецификации всех подпрограмм в классе Deck:Как остальная часть программы узнает, пуста ли колода? Конечно, программа может отслеживать, сколько карточек она использовала. Но сама колода должна знать, сколько карт в ней осталось, поэтому программа должна просто иметь возможность запрашивать объект колоды. Мы можем сделать это возможным, указав другой метод экземпляра, cardsLeft (), который возвращает количество карт, оставшихся в колоде. Это приводит к полной спецификации всех подпрограмм в классе Deck:Как остальная часть программы узнает, пуста ли колода? Конечно, программа может отслеживать, сколько карточек она использовала. Но сама колода должна знать, сколько карт в ней осталось, поэтому программа должна просто иметь возможность запрашивать объект колоды. Мы можем сделать это возможным, указав другой метод экземпляра, cardsLeft (), который возвращает количество карт, оставшихся в колоде. Это приводит к полной спецификации всех подпрограмм в классе Deck:Это приводит к полной спецификации всех подпрограмм в классе Deck:Это приводит к полной спецификации всех подпрограмм в классе Deck:

Это все, что вам нужно знать, чтобы использоватькласс Deck. Конечно, он не говорит нам, как писать класс. Это было упражнение в дизайне, а не в кодировании. Вы можете посмотреть исходный код Deck.java, если хотите. Неудивительно, что класс включает в себя массив Cards в качестве переменной экземпляра, но есть несколько вещей, которые вы можете не понять на данный момент. Конечно, вы можете использовать класс в своих программах как черный ящик, не разбираясь в реализации.

Мы можем провести аналогичный анализ для класса Hand. Когда объект руки создается впервые, в нем нет карт. Метод экземпляра addCard () добавит карту в руку. Этому методу необходим параметр типа Card, чтобы указать, какая карта добавляется. Для метода removeCard () необходим параметр, чтобы указать, какую карту удалить. Но следует ли указывать саму карту («Убрать туз пик») или указать карту по ее положению в руке («Убрать третью карту в руке»)? На самом деле нам не нужно принимать решение, поскольку мы можем учитывать оба варианта. У нас будет два метода экземпляра removeCard (), один с параметром типа Card, указывающим карту, которую нужно удалить, а другой с параметром типа int, определяющим положение карты в руке.(Помните, что у вас может быть два метода в классе с одинаковым именем, при условии, что они имеют разные числа или типы параметров.) Поскольку рука может содержать переменное количество карт, удобно иметь возможность спросить у объекта руки, сколько карты, которые он содержит. Итак, нам нужен метод экземпляра getCardCount (), который возвращает количество карт в руке. Когда я играю в карты, мне нравится располагать карты в руке так, чтобы карты одного достоинства находились рядом друг с другом. Поскольку это обычно полезная вещь, мы можем предоставить методы экземпляра для сортировки карт в руке. Вот полная спецификация многоразового класса Hand:нам нужен метод экземпляра getCardCount (), который возвращает количество карт в руке. Когда я играю в карты, мне нравится располагать карты в руке так, чтобы карты одного достоинства находились рядом друг с другом. Поскольку это обычно полезная вещь, мы можем предоставить методы экземпляра для сортировки карт в руке. Вот полная спецификация многоразового класса Hand:нам нужен метод экземпляра getCardCount (), который возвращает количество карт в руке. Когда я играю в карты, мне нравится располагать карты в руке так, чтобы карты одного достоинства находились рядом друг с другом. Поскольку это обычно полезная вещь, мы можем предоставить методы экземпляра для сортировки карт в руке. Вот полная спецификация многоразового класса Hand:

Опять же, в реализации класса есть несколько вещей, которые вы пока не поймете, но это не мешает вам использовать класс в ваших проектах. Исходный код можно найти в файле Hand.java.

5.4.2 Класс карты

Мы подробно рассмотрим дизайн и реализацию класса Card. У класса будет конструктор, определяющий значение и масть создаваемой карты. Существует четыре масти, которые могут быть представлены целыми числами 0, 1, 2 и 3. Было бы сложно запомнить, какое число представляет какую масть, поэтому я определил именованные константы в классе Card для представления четырех возможностей. Например, Card.SPADES - это константа, представляющая масть «пики». (Эти константы объявлены как общедоступные конечные статические целые числа. Возможно, лучше использовать перечислимый тип, но я буду придерживаться здесь целочисленных констант.) Возможные значения карты - это числа 1, 2,. 13, где 1 соответствует тузу, 11 - валету, 12 - даме и 13 - королю. Опять же, яМы определили некоторые именованные константы для представления значений тузов и лицевых карт. (Когда вы прочитаете класс Card, вы увидите, что я также добавил поддержку Jokers.)

Объект Card может быть построен, зная значение и масть карты. Например, мы можем вызвать конструктор с такими операторами, как:

Объекту Card требуются переменные экземпляра для представления его значения и масти. Я сделал их частными, чтобы их нельзя было изменить извне класса, и я предоставил методы получения getSuit () и getValue (), чтобы можно было обнаружить костюм и значение вне класса. Переменные экземпляра инициализируются в конструкторе и никогда не меняются после этого. Фактически, я объявил переменные экземпляра suit и value окончательными, поскольку они никогда не меняются после инициализации. Переменная экземпляра может быть объявлена ​​окончательной при условии, что ей либо присвоено начальное значение в ее объявлении, либо она инициализирована в каждом конструкторе в классе. Поскольку все его переменные экземпляра являются final, Card является неизменяемым объектом.

Наконец, я добавил в класс несколько удобных методов, чтобы упростить распечатку карточек в удобочитаемой форме. Например, я хочу иметь возможность распечатать масть карты как слово «Бриллианты», а не как бессмысленное кодовое число 2, которое используется в классе для обозначения бриллиантов. Поскольку это то, что мне, вероятно, придется делать во многих программах, имеет смысл включить поддержку этого в классе. Итак, я предоставил методы экземпляра getSuitAsString () и getValueAsString () для возврата строковых представлений масти и значения карты. Наконец, я определил метод экземпляра toString () для возврата строки со значением и мастью, например «Королева червей». Напомним, что этот метод будет использоваться автоматически всякий раз, когда Card необходимо преобразовать в String,например, когда карта присоединяется к строке с помощью оператора +. Таким образом, заявление

эквивалентно

Если карта - дама червей, на любой из них будет напечатано «Ваша карта - дама червей».

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

5.4.3 Пример: простая карточная игра

Я закончу этот раздел, представив полную программу, в которой используются классы Card и Deck. Программа позволяет пользователю играть в очень простую карточную игру под названием HighLow. Колода карт перемешивается, и одна карта раздается из колоды и показывается пользователю. Пользователь предсказывает, будет ли следующая карта из колоды выше или ниже текущей карты. Если пользователь предсказывает правильно, то следующая карта из колоды становится текущей картой, и пользователь делает другое предсказание. Это продолжается до тех пор, пока пользователь не сделает неверный прогноз. Количество правильных прогнозов - это оценка пользователя.

В моей программе есть статический метод, который воспроизводит одну игру HighLow. Подпрограмма main () позволяет пользователю играть в несколько игр HighLow. В конце он сообщает средний балл пользователя.

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