Рейтинговые книги
Читем онлайн Философия Java3 - Брюс Эккель

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 48 49 50 51 52 53 54 55 56 ... 132

}

}

class CheckersFactory implements GameFactory {

public Game getGameO { return new CheckersO; }

}

class Chess implements Game { private int moves = 0; private static final int MOVES = 4; public boolean moveO {

print("Chess move " + moves); return ++moves != MOVES;

}

}

class ChessFactory implements GameFactory {

public Game getGameO { return new ChessO; }

}

public class Games {

public static void playGame(GameFactory factory) {

Game s = factory.getGameO; продолжение & while(s.moveO)

}

public static void main(String[] args). { playGame(new CheckersFactoryO); playGame(new ChessFactoryO);

}

} /* Output: Checkers move 0 Checkers move 1 Checkers move 2 Chess move 0 Chess move 1 Chess move 2 Chess move 3 *///:-

Если класс Games представляет сложный блок кода, такое решение позволит повторно использовать его для разных типов игр.

В следующей главе будет представлен более элегантный способ реализации фабрик на базе анонимных внутренних классов.

Резюме

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

Многие программисты поддались этому искушению. Тем не менее любая абстракция должна быть мотивирована реальной потребностью. Основное назначение интерфейсов — возможность переработки реализации в случае необходимости, а не введение лишнего уровня абстракции вместе с дополнительными сложностями. Дополнительные сложности могут оказаться довольно существенными. А представьте, что кто-то будет вынужден разбираться в вашем коде и в конечном итоге поймет, что интерфейсы были добавлены «на всякий случай», без веских причин — в таких ситуациях все проектирование, которое выполнялось данным разработчиком, начинает выглядеть довольно сомнительно.

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

Внутренние (j

классы кМэ vv

Определение класса может размещаться внутри определения другого класса. Такие классы называются внутренними (inner class).

Внутренние классы весьма полезны, так как они позволяют группировать классы, логически принадлежащие друг другу, и управлять доступом к ним. Однако следует понимать, что внутренние классы заметно отличаются от композиции.

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

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

Создание внутренних классов

Внутренние классы создаются в точности так, как и следовало ожидать, — определение класса размещается внутри окружающего класса:

//: innerclasses/Parcel 1.java // Создание внутренних классов.

public class Parcel 1 {

class Contents {

private int i = 11,

public int valueO { return i; }

}

class Destination {

private String label, DestinationCString whereTo) { label = whereTo;

}

String readLabeK) { return label; }

}

// Использование внутренних классов имеет много общего // с использованием любых других классов в пределах Parcel 1: public void shipCString dest) {

Contents с = new ContentsO; Destination d = new Destination(dest); System.out.pri ntln(d readLabel()),

}

public static void main(String[] args) { Parcel 1 p = new Parcel 10. p.ship("Тасмания").

}

} /* Output: Тасмания *///:-

Если вам понадобится создать объект внутреннего класса где-либо, кроме как в не-статическом методе внешнего класса, тип этого объекта должен задаваться в формате ИмяВнешнегоКласса.ИмяВнутреннегоКласса, что и делается в методе main().

Связь с внешним классом

Пока что внутренние классы выглядят как некоторая схема для сокрытия имен и организации кода — полезная, но не особенно впечатляющая. Однако есть еще один нюанс. Объект внутреннего класса связан с внешним объектом-создателем и может обращаться к его членам без каких-либо дополнительных описаний. Вдобавок для внутренних классов доступны все без исключения элементы внешнего класса14. Следующий пример иллюстрирует сказанное:

//: innerclasses/Sequence,java // Хранение последовательности объектов

interface Selector { boolean endO, Object currentO; void nextO;

}

public class Sequence {

private Object[] objects;

private int next = 0,

public Sequence(int size) { items = new Object[size], }

public void add(Object x) {

if(next < items length)

iterns[next++] = x,

}

private class SequenceSelector implements Selector { private int i = 0.

public boolean endО { return i == items.length; } public Object current О { return i terns [ i D: } public void nextО { if(i < items.length) i++; }

}

public Selector selectorO {

return new SequenceSelectorO;

}

public static void main(String[] args) {

Sequence sequence = new Sequence(lO); for(int i = 0, i < 10; i++)

sequence.add(Integer.toString(i)), Selector selector = sequence getSelectorO; whileC!selector endO) {

System.out.println(selector.currentО + " "); selector.nextO;

}

}

} /* Output-0 1 2 3 4 5 6 7 8 9 *///:-

Класс Sequence — не более чем «оболочка» для массива с элементами Object, имеющего фиксированный размер. Для добавления новых объектов в конец последовательности (при наличии свободного места) используется метод add(). Для выборки каждого объекта в последовательности Sequence предусмотрен интерфейс с именем Selector. Он позволяет узнать, достигнут ли конец последовательности (метод end()), обратиться к текущему объекту (метод current()) и перейти к следующему объекту последовательности (метод next()). Так как Selector является интерфейсом, другие классы вправе реализовать его по-своему, а передача его в параметре методов повышает универсальность кода.

Здесь SequenceSelector является закрытым (private) классом, предоставляющим функциональность интерфейса Selector. В методе main() вы можете наблюдать за процессом создания последовательности с последующим заполнением ее объектами String. Затем вызывается метод getSelector() для получения интерфейса Selector, который используется для перемещения по последовательности и выбора ее элементов.

На первый взгляд создание SequenceSelector напоминает создание обычного внутреннего класса. Но присмотритесь к нему повнимательнее. Заметьте, что в каждом из методов end(), current() и next() присутствует ссылка на items, а это не одно из полей класса SequenceSelector, а закрытое (private) поле объемлющего класса. Внутренний класс может обращаться ко всем полям и методам внешнего класса-оболочки, как будто они описаны в нем самом. Это весьма удобно, и вы могли в этом убедиться, изучая рассмотренный пример.

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

Конструкции .this и .new

Если вам понадобится получить ссылку на объект внешнего класса, запишите имя внешнего класса, за которым следует точка, а затем ключевое слово this. Полученная ссылка автоматически относится к правильному типу, известному и проверяемому на стадии компиляции, поэтому дополнительные издержки на стадии выполнения не требуются. Следующий пример показывает, как использовать конструкцию .this:

//: innerclasses/DotThis.java

// Обращение к объекту внешнего класса.

public class DotThis {

void f() { System.out.pnntlnCDotThis.fО"); } public class Inner {

public DotThis outer() {

return DotThis this.

// A plain "this" would be Inner's "this"

}

}

public Inner inner О { return new InnerO; } public static void main(String[] args) { DotThis dt = new DotThisO; DotThis Inner dti = dt.innerO; dti.outer().f();

}

} /* Output:

DotThis.f()

*///:-

Иногда бывает нужно приказать другому объекту создать объект одного из его внутренних классов. Для этого перед .new указывается ссылка на другой объект внешнего класса:

//: innerclasses/DotNew.java

// Непосредственное создание внутреннего класса в синтаксисе .new

public class DotNew {

public class Inner {}

public static void main(String[] args) {

DotNew dn - new DotNew(); DotNew.Inner dni = dn.new InnerO;

}

} III -

При создании объекта внутреннего класса указывается не имя внешнего класса DotNew, как можно было бы ожидать, а имя объекта внешнего класса. Это также решает проблему видимости имен для внутреннего класса, поэтому мы не используем (а вернее, не можем использовать) запись вида dn.new DotNew. Inner().

1 ... 48 49 50 51 52 53 54 55 56 ... 132
На этой странице вы можете бесплатно читать книгу Философия Java3 - Брюс Эккель бесплатно.
Похожие на Философия Java3 - Брюс Эккель книги

Оставить комментарий