Выбрать главу

Интерфейсы и фабрики

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

//: interfaces/Factories.java

import static net.mindview.util.Print.*;

interface Service { void methodic); void method2();

}

interface ServiceFactory { Service getServiceO;

}

class Implementationl implements Service {

ImplementationlO {} // Доступ в пределах пакета public void methodic) {print("Implementationl methodl");}

public void method2() {print("Implementationl method2");} } • . .

class ImplementationlFactory implements ServiceFactory { public Service getServiceO {

return new ImplementationlO;

}

}

class Implementation2 implements Service {

Implementation2() {} // Доступ в пределах пакета publ/ic void methodlО {print("Implementation2 methodl");} public void method2() {print("Implementation2 method2");}

}

class Implementation2Factory implements ServiceFactory { public Service getService.O {

return new Implementation2();

}

}

public class Factories {

public static void serviceConsumer(ServiceFactory fact) { Service s = fact.getServiceO;

s methodic). s.method2();

}

public static void main(String[] args) {

serviceConsumer(new ImplementationlFactoryO); // Реализации полностью взаимозаменяемы serviceConsumec(new Implementation2FactoryO);

}

} /* Output. Implementation! methodl Implementationl method2 Implementation2 methodl Implementation2 method2 *///:-

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

Но зачем вводить лишний уровень абстракции? Данный паттерн часто при­меняется при создании библиотек. Допустим, вы создаете систему для игр, ко­торая позволяла бы играть в шашки и шахматы на одной доске:

//: interfaces/Games.java

// Игровая библиотека с использованием фабрики

import static net.mindview.util.Print.*:

interface Game { boolean moveO; } interface GameFactory { Game getGameO; }

class Checkers implements Game { private int moves = 0: private static final int MOVES = 3: public boolean motfeO {

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

}

}

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 представляет сложный блок кода, такое решение позволит повторно использовать его для разных типов игр.

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

Резюме

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

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

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

Определение класса может размещаться внутри определения другого класса. Такие классы называются внутренними (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().

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

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