Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Объектно-ориентированное программирование.PDF
Скачиваний:
209
Добавлен:
01.05.2014
Размер:
3.64 Mб
Скачать

converted to PDF by BoJIoc

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

 

CardPile

SuitPile

DeckPile

DiscardPile

TablePile

includes

*

 

 

 

*

canTake

*

*

 

 

*

addCard

*

 

 

*

 

display

*

 

 

 

*

select

*

 

*

*

*

8.4.1. Основание SuitPile

Мы детально рассмотрим каждый из подклассов CardPile, заостряя внимание на различных свойствах объектно-ориентированного программирования по мере их проявления. Самый простой подкласс это основания SuitPile. Он показан в

листинге 8.6. Стопка лежит в верхнем углу стола, в ней находятся карты одной масти от туза до короля.

Листинг 8.6. Класс SuitPile

class SuitPile extends CardPile

{

SuitPile (int x, int y)

{

super(x, y);

}

public boolean canTake (Card aCard)

{

if (empty())

return aCard.rank() == 0; Card topCard = top();

return (aCard.suit() == topCard.suit()) && (aCard.rank() == 1 + topCard.rank());

}

}

Класс SuitPile определяет только два метода. Его конструктор берет два целочисленных аргумента и не делает ничего, кроме вызова конструктора надкласса CardPile. Обратите внимание на ключевое слово super, указывающее родительский класс. Метод canTake определяет, можно или нет поместить карту в стопку. Перемещение карты законно, если стопка пуста и эта карта туз или если эта карта той же масти, что и верхняя карта в стопке, и ее ранг следующий по старшинству (например, тройка пик может быть положена только на двойку пик).

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

8.4.2. Колода DeckPile

Класс DeskPile (листинг 8.7) обслуживает исходную колоду карт. Она отличается от стопки карт общего типа двумя моментами. При конструировании экземпляра вместо пустой стопки класс создает полную колоду из 52 карт, вставляя их в случайном порядке в связный список. Подпрограмма random библиотеки языка Java генерирует случайную величину с двойной точностью в диапазоне от 0 до 1. Она преобразуется в случайное целое число во время процесса тасования колоды.

converted to PDF by BoJIoc

Метод select вызывается, когда щелчок мыши производится над колодой DeskPile. Если она пуста, то ничего не происходит. В противном случае верхняя карта удаляется из колоды и добавляется в промежуточную стопку.

В языке Java нет глобальных переменных. Когда значение используется несколькими объектами классов (такими, как разные стопки карт в нашем пасьянсе), переменная объявляется с ключевым словом static. Как мы увидим в главе 20, при этом создается одна копия статической переменной, которая доступна всем экземплярам. В данной программе статические переменные применяются для хранения различных стопок карт. Они будут содержаться в экземпляре класса Solitare, который мы опишем впоследствии. Для доступа к ним мы используем полностью специфицированное имя, которое кроме имени переменной включает название класса. Это показано в методе select

(листинг 8.8), который обращается к переменной Solitare.discardPile для доступа к промежуточной стопке.

Листинг 8.7. Класс DeckPile

class DeckPile extends CardPile {

DeckPile (int x, int y)

{

//сначала инициализируется надкласс super(x, y);

//затем создается новая колода

//сначала она кладется в локальную стопку

CardPile pileOne = new CardPile(0, 0); CardPile pileTwo = new CardPile(0, 0); int count = 0;

for (int i = 0; i < 4; i++)

{

pileOne.addCard(new CArd(i, j)); count++;

}

//затем случайно вытаскивается карта

for (; count > 0; count--)

{int limit = ((int)(Math.random() * 1000))

%count;

//перемещается вниз в случайное место for (int i = 0; i < limit; i++)

pileTwo.addCard(pileOne.pop());

//потом добавляется карта отсюда addCard(pileOne.pop());

//затем колоды складываются обратно

while (! pileTwo.empty()) pileOne.addCard(pileTwo.pop());

}

}

public void select(int tx, int ty)

{

if (empty()) return;

Solitaire.discardPile.addCard(pop());

}

}

converted to PDF by BoJIoc

8.4.3. Промежуточная стопка DiscardPile

Класс DiscardPile (см. листинг 8.8) интересен тем, что он демонстрирует две совершенно разные формы наследования. Метод select замещает или переопределяет поведение, по умолчанию обеспечиваемое классом CardPile. Новый код при вызове (то есть при нажатии кнопки мыши в области стопки) проверяет, может ли верхняя карта быть перемещена на какое-нибудь основание или на одну из стопок расклада. Если карта не может быть перемещена, она остается в промежуточной стопке.

Метод addCard демонстрирует другой тип переопределения. Здесь поведение уточняет функциональность надкласса. То есть полностью отрабатывается поведение надкласса и, кроме того, добавляется новое поведение. В данном случае новый код гарантирует, что когда карта лежит в промежуточной стопке, она всегда будет смотреть картинкой вверх. После того как это условие удовлетворено, путем посылки сообщения для псевдопеременной super вызывается код надкласса, который добавляет карту в стопку.

Другая форма уточнения возникает для конструкторов различных подклассов. До того как конструктор выполнит свои собственные действия, каждый из них должен вызвать конструктор надкласса, дабы гарантировать, что предок инициализировался должным образом. Конструктор предка вызывается через псевдо-переменную super; он вызывается как функция внутри конструктора дочернего класса. В главе 11 мы поговорим подробнее о различии между замещением и уточнением при переопределении методов.

Листинг 8.8. Класс DiscardPile

class discardPile extends CardPile

{

DiscardPile (int x, int y)

{

super (x, y);

}

public void addCard (Card aCard)

{

if (! aCard.faceUp()) aCard.flip();

super.addCard(aCard);

}

public void select (int tx, int ty)

{

if (empty()) return;

Card topCard = pop();

for (int i = 0; i < 4; i++)

{

if (Solitaire.suitPile[i].canTake(topCard))

{

Solitaire.suitPale[i].addCard(topCard);

return;

}

}

for (int i = 0; i < 7; i++)

{

if (Solitaire.tableau[i].canTake(topCard))

{

Solitaire.tableau[i].addCard(topCard);

return;

}

}

converted to PDF by BoJIoc

// никто не может ее использовать,

//положим ее назад addCard(topCard);

}

}

8.4.4. Стопка расклада TablePile

Наиболее сложный из подклассов класса CardPile это тот, который используется для хранения стопок расклада TablePile. Он показан в листингах 8.9 и 8.10. Стопки расклада отличаются от стопок карт общего назначения следующими моментами:

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

§Карта может быть добавлена в стопку (проверяется методом canTake), только если стопка пуста и эта карта король или если карта оказывается противоположного

цвета по сравнению с текущей верхней картой стопки и ее ранг на единицу меньше, чем ранг верхней карты.

§Для проверки попадания щелчка мыши в пределы области стопки (метод includes) учитываются только левая, правая и верхняя границы. Нижняя граница игнорируется, так как стопка расклада TablePile может быть переменной длины.

§При щелчке мышью в пределах стопки карт расклада верхняя карта открывается, если она была закрыта. Если карта открыта, то делается попытка переместить ее сперва в какое-нибудь основание, а затем в какую-нибудь стопку расклада.

Только если ни одна из стопок не может принять карту, она остается на месте.

§Для отображения стопки на экране карты в ней рисуются друг за другом, так что каждая следующая карта понемногу сдвигается вниз. Для того чтобы сделать это с нижней до верхней карты включительно, небольшая рекурсивная подпрограмма (объявленная как private) просматривает весь связный список, отображая карты в тот момент, когда управление возвращается назад после рекурсивного вызова. Эта функция использует итератор для цикла по элементам списка.

8.5. Полиморфная игра

Как мы уже видели в «Задаче о восьми ферзях» (глава 5), среда для всех приложений на языке Java обеспечивается классом Applet. Для создания нового приложения программист определяет подклассы Applet, переопределяя при этом различные методы. Класс Solitare, который является центральным классом нашего приложения, показан в

листинге 8.11.

Мы ранее уже отмечали, что переменные, которые хранят общие для всех объектов данные, объявляются с ключевым словом static. Такие поля инициализируются в методе init класса1.

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

converted to PDF by BoJIoc

Листинг 8.9. Класс TablePile, часть I

class TablePile extends CardPile {

TablePile (int x, int y, int c)

{

// инициализация надкласса super(x, y);

//затем инициализируется наша стопка карт for (int i = 0; i < c; i++)

{

addCard(Solitaire.deckPile.pop());

}

//верхняя карта открывается

top.flip();

}

public boolean cantake (Card aCard)

{

if (empty())

return aCard.rank() == 12; Card topCard = top();

return (aCard.color() != topCard.color()) && (aCard.rank() == topCard.rank() — 1);

}

public boolean includes (int tx, int ty)

{

// не проверяет нижнюю границу

return x < = tx && tx<= x + Card.width && y <= ty;

}

private int stackDisplay (Graphics g, ListIterator itr)

{

int localy;

if (itr.atEnd()) return y;

Card aCard = (Card) itr.current; itr.next;

localy = stackDisplay(g, itr); aCard.draw(g, x, localy); return localy + 35;

}

...

Листинг 8.10. Класс TablePile, часть II

class TablePile extends CardPile

{

...

public void select (int tx, int ty)

{

if (empty()) return;

// если карта закрыта, перевернуть

Card topCard = top(); if (! topCard.faceUp())

{

converted to PDF by BoJIoc

topCard.flip();

return;

}

// иначе смотрим, можно ли ее положить в основание topCard = pop();

for (int i = 0; i < 4; i++)

{

if (Solitaire.suitPile[i].canTake(topCard))

{

Solitaire.suitPile[i].addCard(topCard);

return;

}

}

//нельзя ли положить в другую стопку расклада for (int i = 0; i < 7; i++)

{

if (Solitaire.tableau[i].canTake(topCard))

{

Solitaire.tableau[i].addCard(topCard);

return;

}

}

//иначе кладем обратно

addCard(topCard);

}

public void display (Graphics g)

{

stackDisplay(g, cardList.iterator());

}

}

Листинг. 8.11. Класс Solitaire

public class Solitaire extends Applet

{

static DeckPile deckPile; static DisacrdPile discardPile; static TablePile tableau [ ]; static SuitPile suitPile [ ]; static CardPile allPiles [ ]; public void init()

{

//сначала отводим место под массивы allPiles = new CardPile[13]; suitPile = new SuitPile[4];

tableau = new TablePile[7];

//затем заполняем их данными

allPiles[0] = deckPile = new DeckPile(335, 5); allPiles[1] = discardPile =

new DiscardPile(268, 5); for (int i = 0; i < 4; i++)

{ allPiles[2+i] = suitPile[i] = new SuitPile(15 + 60 * i, 5);

}

for (int i = 0; i < 7; i++)

{allPiles[6+i] = tableau[i] =

new TablePile(5 + 55 * i, 80, i+1);

}

converted to PDF by BoJIoc

}

public void paint(Graphics g)

{

for (int i = 0; i < 13; i++) { allPiles[i].display(g); }

}

public boolean mouseDown(Event evt, int x, int y)

{

for (int i = 0; i < 13; i++)

{

if (allPiles[i].includes(x, y))

{allPiles[i].select(x, y);

repaint(); return true;

}

}

return true;

}

}

показывает только то, что объекты являются массивами; про их границы ничего не говорится. Один из первых шагов процедуры инициализации выделение места под три массива (основания, стопки расклада и массив allPiles, который мы рассмотрим ниже). Команда new отводит память для этих массивов, но не присваивает никаких значений их элементам.

Следующий шаг создание колоды DeskPile. Вспомните, что конструктор этого класса генерирует и перетасовывает полную колоду из 52 карт. Промежуточная стопка DiscardPile создается аналогичным образом. Затем в цикле порождаются и инициализируются четыре основания SuitPile, а второй цикл создает и инициализирует стопки расклада TablePile. Вспомните, что при инициализации стопок расклада карты берутся из колоды и вставляются в стопку расклада.

Массив allPiles используется для представления всех 13 стопок карт. Заметьте, что как только создается очередная стопка, ей тут же присваивается ячейка в этом массиве, равно как и соответствующая статическая переменная. Мы воспользуемся этим массивом для иллюстрации еще одного аспекта наследования. Следуя принципу подстановки, allPiles объявлен как массив из элементов с типом данных CardPile, но на самом деле он содержит стопки карт разнообразного вида.

Данный массив используется в ситуациях, когда различия между типами стопок карт не важны. Например, в процедуре перерисовки экрана каждую стопку просто просят самостоятельно перерисовать себя. Похожим образом при щелчке мышью опрашивается каждая стопка, не содержит ли она указанную точку экрана. Если да, то стопка выделяется. Среди них есть семь стопок расклада, четыре основания, промежуточная стопка и колода. Более того, фактический код, исполняемый в ответ на вызов методов select и includes, может различаться в зависимости от типа обрабатываемой стопки.

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

8.6. Создание более сложной игры

Пасьянс, описанный здесь, обладает минимальными возможностями и в нем чрезвычайно трудно выиграть. Более реалистичная игра включала бы по крайней мере некоторые из следующих вариаций: