Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Джош Блох

.pdf
Скачиваний:
57
Добавлен:
08.03.2016
Размер:
27.13 Mб
Скачать

Глава 2 Создание иуничтожение объектов

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

В момент, когда пишется класс, содержащий статический метод генерации, класс, соответствующий возвращаемому объекту, может даже не существовать. Подобные гибкие статические методы генера­ ции лежат в основе систем с предоставлением услуг (service provider frameworks) , таких как Java Cryptography Extension (JC E ). Система

спредоставлением услуг — это такая система, где поставщик может создавать различные реализации интерфейса API, доступные поль­ зователям этой системы. Чтобы сделать эти реализации доступными для использования, предусмотрен механизм регистрации {register). Клиенты могут пользоваться указанным API, не беспокоясь о том,

скакой из его реализаций они имеют дело.

Укласса java. util. EnumSet (статья 32), представленного в вер­ сии 1.3, нет открытых конструкторов, только статические методы. Они возвращают одну из двух реализаций в зависимости от размера типа перечисления: если значение равно 64 и менее элементов (как у боль­ шей части типов перечислений), то статический метод возвращает эк­ земпляр RegularEnumSet, подкрепленный единичным значением long. Если же тип перечислений содержит 63 и более элементов, то метод возвращает экземпляр JumboEnumSet, подкрепленный массивом long.

Существование двух реализаций классов невидимо для клиен­ тов. Если экземпляр RegularEnumSet перестанет давать преимущество

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

Ю

С татья 1

Классу объекта, возвращаемого статическим методом, нет необ­ ходимости существовать на момент написания класса, содержащего метод. Подобная гибкость методов генерации создает основу для си­ стем предоставления услуг (service provider frameworks), таких как

Java Database Connectivity API (JDBC). Система предоставления

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

Имеется три основных компонента системы предоставления услуг: интерфейс службы (service interface), который предоставля­ ется поставщиком, интерфейс регистрации поставщика (provider registration A P I), который использует система для регистрации ре­ ализации, и интерфейс доступа к службе (service access A P I), кото­ рый используется клиентом для получения экземпляра службы. Ин­ терфейс доступа к службе обычно позволяет определить некоторые критерии для выбора поставщика, которые тем не менее не являются обязательными. При отсутствии таковых он возвращает экземпляр реализации по умолчанию. Интерфейс доступа к службе — это «гиб­ кий производственный метод», составляющий основу системы пре­ доставления услуг.

Есть еще не обязательный четвертый компонент службы предо­ ставления услуг — интерфейс поставщика службы (service provider interface), который внедряется провайдером для создания экземпля­ ров реализации их служб. При отсутствии этого интерфейса реали­ зации регистрируются по имени класса, а их экземпляры создаются рефлективным образом (статья 53). В случае с JD B C Connection здесь играет роль интерфейса службы, DriverManager. registerDriv­ er — интерфейс регистрации провайдера, DriverManager.getConnection - интерфейс доступа к службе, и Driver - интерфейс постав­ щика службы.

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

11

Глава 2 Создание и уничтожение объектов

при использовании паттерна «Адаптер» [Gamma 95, с. 139], Здесь приведена простая реализация с интерфейсом службы поставщика

ипоставщиком по умолчанию:

//Service provider framework sketch

//Service interface

public interface Service {

// Service-specific methods go here

}

// Service provider interface public interface Provider {

Service newService();

}

// Noninstantiable class for service registration and access

public class Services

{

 

private

ServicesO

{ } // Prevents instantiation (Item 4)

// Maps

service

names

to services

private

static

final

Map<String, Provider> providers =

new ConcurrentHashMap<String, Provider>();

public static final String DEFAULT_PROVIDER_NAME = "<def>”;

// Provider registration API

public static void registerDefaultProvider(Provider p) { registerProvider(DEFAULT_PROVIDER_NAME, p);

}

public static void registerProvider(String name, Provider p){ providers.put(name, p);

}

// Service access API

public static Service newlnstance() {

return newInstance(DEFAULT_PROVIDER_NAME);

}

12

С татья 1

public static Service newInstance(String name) { Provider p = providers.get(name);

if (p == null)

throw new IllegalArgumentException(

“No provider registered with name: “ + name); return p.newService();

}

}

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

Map<String, List<String>> m =

new HashMap<String, List<String»();

Эта излишняя спецификация становится проблемой по мере уве­ личения сложности параметров типов. При использовании же стати­ ческих методов компилятор сможет за вас создать параметры. Это еще называется (type inference). Например, предположим, что реа­ лизация HashМар дала нам следующий метод (статья 27):

public static <К, V> HashMap<K, V> newlnstance() { return new HashMap<K, V>();

}

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

Map<String, List<String>> m = HashMap.newlnstance();

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

К сожалению, у стандартного набора реализаций, таких как HashMap, нет своих статических методов в версии 1.6, но вы можете доба-

13

Глава 2 Создание и уничтожение объектов

вить эти методы в собственный класс параметров. Теперь вы сможете предоставлять статические методы генерации в собственных классах с параметрами.

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

Второй недостаток статических методов генерации состоит в том, что их трудно отличить от других статических методов.

В документации A PI они не выделены так, как это было бы сде­ лано для конструкторов. Поэтому иногда из документации к клас­ су сложно понять, как создать экземпляр класса, в котором вместо конструкторов клиенту предоставлены статические методы генера­ ции. Возможно, когда-нибудь в официальной документации по Java будет уделено должное внимание статическим методам. Указанный недостаток может быть смягчен, если придерживаться стандартных соглашений, касающихся именования. Эти названия статических ме­ тодов генерации становятся общепринятыми:

valueOf — возвращает экземпляр, который, грубо говоря,

имеет то же значение, что и его параметры. Статические ме­ тоды генерации с таким названием фактически являются опе­ раторами преобразования типов.

of — более краткая альтернатива для valueOf, распростра­

ненная при использовании EnumSet (статья 32).

getlnstance — возвращает экземпляр, который описан пара­ метрами, однако говорить о том, что он будет иметь то же

14

С татья 2

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

newlnstance — то же, что и getlnstance, только newlnstance

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

getType — то же, что и getlnstance, но используется, когда

метод генерации находится в другом классе. Туре обозначает тип объекта, возвращенного методом генерации.

newType — то же, что и newlnstance, но используется, когда

метод генерации находится в другом классе. Туре обозначает тип объекта, возвращенного методом генерации.

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

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

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

15

Глава 2 Создание и уничтожение объектов

размер порции, количество порций в упаковке, калорийность, а так­ же ряд необязательных параметров — общее содержание жиров, со­ держание насыщенных жиров, содержание жиров с трансизомерами жирных кислот, содержание холестерина, натрия и т.д. У большин­ ства продуктов не нулевыми будут только несколько из этих необя­ зательных значений.

Какой конструктор или какие методы нужно использовать для на­ писания данного класса? Традиционно, программисты будут исполь­ зовать шаблоны с телескопическими конструкторами (Telescoping Constructor Pattern), при использовании которых вы выдаете набор конструкторов: конструктор с одними обязательными параметра­ ми, конструктор с одним необязательным параметром, конструктор с двумя обязательными параметрами и т.д., до тех пор пока не будет конструктора со всеми необязательными параметрами. Вот как это выглядит на практике. Для краткости мы будем использовать только

4не обязательных параметра:

//Telescoping constructor pattern - does not scale well! public class NutritionFacts {

private final int servingSize; // (ml_) required

private final int servings: // (per container) required private final int calories; // optional

private final int fat; // (g) optional private final int sodium; // (mg) optional

private final int carbohydrate; // (g) optional

public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0);

}

public

NutritionFacts(int

servingSize,

int servings,

int

calories)

{

 

 

 

 

 

 

this(servingSize,

servings,

calories,

0);

}

 

 

 

 

 

 

 

public

NutritionFacts(int

servingSize,

int servings,

int

calories,

int fat) {

 

 

 

 

 

 

this(servingSize,

servings,

calories,

fat, 0);

16

С татья 2

}

public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {

this(servingSize, servings, calories, fat, sodium, 0);

}

public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {

this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat;

this.sodium = sodium; this.carbohydrate = carbohydrate;

}

}

Если вы хотите создать экземпляр данного класса, то вы будете использовать конструктор с минимальным набором параметров, ко­ торый бы содержал все параметры, которые вы хотите установить:

NutritionFacts cocaCola =

new NutritionFacts(240, 8, 100, 0, 35, 27);

Обычно для вызова конструктора потребуется передавать мно­ жество параметров, которые вы не хотите устанавливать, но вы в лю­ бом случае вынуждены передать для них значение. В нашем случае мы установили значение 0 для поля fat. Поскольку мы имеем толь­ ко шесть параметров, может показаться, что это не так уж и плохо, но ситуация выходит из-под контроля, когда число параметров уве­ личивается.

Короче говоря, шаблоны телескопических конструкторов нормально работают, но становится трудно писать код програм­ мы-клиента, когда имеется много параметров, а еще труднее этот код читать. Читателю остается только гадать, что означают все эти значения, и нужно тщательно высчитывать позицию параметра, чтобы выяснить, к какому полю он относится. Длинные последова­

Глава 2 Создание иуничтожение объектов

тельности одинаково типизированных параметров могут приводить к тонким ошибкам. Если клиент случайно перепутает два из таких параметров, то компиляция будет успешной, но программа будет не­ правильно работать при выполнении (статья 40).

Второй вариант, когда вы столкнулись с конструктором со мно­ гими параметрами, — это использование шаблонов JavaBeans, где вы вызываете конструктор без параметров, чтобы создать объект, а затем вызываете сеттеры для установки обязательных и всех инте­ ресующих необязательных параметров:

// JavaBeans Pattern - allows inconsistency, mandates mutability public class NutritionFacts {

// Parameters initialized to default values (if any) private int servingSize = -1; // Required; no default value private int servings = -1; // “ “ “ “

private int calories = 0; private int fat = 0; private int sodium = 0;

private int carbohydrate = 0; public NutritionFacts() { }

// Setters

public void setServingSize(int val) { servingSize = val; } public void setServings(int val) { servings = val; } public void setCalories(int val) { calories = val; } public void setFat(int val) { fat = val; }

public void setSodium(int val) { sodium = val; }

public void setCarbohydrate(int val) { carbohydrate = val; }

}

Данный шаблон лишен недостатков шаблона телескопических конструкторов. Он прост, хотя и содержит большое количество слов, но получившийся код легко читается.

NutritionFacts cocaCola = new NutritionFacts(); cocaCola.setServingSize(240); cocaCola.setServings(8); cocaCola.setCalories(100);

С татья 2

cocaCola.setSodium(35);

cocaCola.setCarbohydrate(27);

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

JavaBean может находиться в неустойчивом состоянии частично из-за такой конструкции. У класса нет возможности принудительно обеспечить стабильность простой проверкой действительности пара­ метров конструктора. Попытка использования объекта, если он нахо­ дится в неустойчивом состоянии, может привести к ошибкам выполне­ ния даже после удаления ошибки из кода, что создает трудности при отладке. Схожим недостатком является то, что шаблон JavaBeans ис­ ключает возможность сделать класс неизменным (статья 15), что требует дополнительных усилий со стороны программиста для обеспе­ чения безопасности в многопоточной среде.

Действие этого недостатка можно уменьшить, вручную «замо­ раживая» объект после того, как его создание завершено, и запре­ тив его использование, пока он заморожен, но этот вариант редко используется на практике. Более того, он может привести к ошиб­ кам выполнения, так как компилятор не может удостовериться в том, вызывает ли программист метод заморозки для объекта до того, как он будет использоваться.

К счастью, есть и третья альтернатива, которая сочетает в себе безопасность шаблона телескопических конструкций и читаемость шаблона JavaBeans. Она является одной из форм шаблона «конструк­ тора» [Gamma 95, с. 97]. Вместо непосредственного создания же­ лаемого объекта клиент вызывает конструктор (или статический ме­ тод) со всеми необходимыми параметрами и получает объект Builder (builder object). Затем клиент вызывает сеттеры на этом объекте для установки всех интересующих параметров. Наконец, клиент вызы­ вает метод build для генерации объекта, который будет являться не­ изменным. «Конструктор» является статическим внутренним клас­ сом в классе (статья 22), который он создает. Вот как это выглядит на практике:

19

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]