Философия Java3 - Брюс Эккель
Шрифт:
Интервал:
Закладка:
Регистрация фабрик
У построения объектов иерархии Pet есть один недостаток: каждый раз, когда в иерархию включается новый тип Pet, вы должны добавить его в LiteralPet-Creator.java. В системах с регулярным добавлением новых классов это может создать проблемы.
Первое, что приходит в голову, — добавить в каждый класс статический инициализатор, который добавлял бы свой класс в некий список. К сожалению, статические инициализаторы вызываются только при первой загрузке класса, поэтому возникает «порочный круг»: класс отсутствует в списке генератора, поэтому генератор не может создать объект этого класса, соответственно, класс не загрузится и не будет помещен в список.
По сути, вы вынуждены создать список вручную (разве что вы напишете утилиту, которая будет анализировать исходный код, а затем создавать и компилировать список). Вероятно, лучшее, что можно сделать, — это разместить список в одном централизованном, очевидном месте. Вероятно, лучшим местом для него будет базовый класс иерархии.
В этом разделе мы также внесем другое изменение: создание объекта будет передано самому классу с использованием паттерна «метод-фабрика». Метод-фабрика может вызываться полиморфно и создает объект соответствующего типа. В следующей упрощенной версии методом-фабрикой является метод create() интерфейса Factory:
//: typeinfo/factory/Factory.java package typeinfo.factory:
public interface Factory<T> { T createO; } ///•-
Обобщенный параметр T позволяет create() возвращать разные типы для разных реализаций Factory. Также при этом используется ковариантность возвращаемых типов.
В следующем примере базовый класс Part содержит список объектов-фабрик. Фабрики типов, которые должны создаваться методом createRandom(), «регистрируются» в базовом классе включением в список partFactories:
//: typeinfo/RegisteredFactories.java II Регистрация фабрик в базовом классе import typeinfo.factory.*: import java util.*:
class Part {
public String toStringO {
return getClass().getSimpleName();
}
static List<Factory<? extends Part» partFactories = new ArrayList<Factory<? extends Part»0;
static {
// При вызове Collections addAllO выдается предупреждение // "unchecked generic array creation for varargs parameter" partFactories.add(new Fuel Filter FactoryO). partFactories add(new AirFilter FactoryO), partFactories add(new CabinAirFilter.FactoryO), partFactories add(new Oil Filter FactoryO); partFactories add(new FanBelt FactoryO); partFactories.add(new PowerSteeringBelt.Factory()), partFactories add(new GeneratorBelt FactoryO),
}
private static Random rand = new Random(47);
public static Part createRandomO {
int n = rand nextInt(partFactories sizeO), return partFactories get(n) createO;
class Filter extends Part {}
class Fuel Filter extends Filter {
// Создание фабрики для каждого конкретного типа
public static class Factory
implements typeinfo factory.Factory<FuelFilter> {
public Fuel Filter createO { return new Fuel Filter О. }
}
}
class AirFilter extends Filter { public static class Factory implements typeinfo factory Factory<AirFilter> {
public AirFilter createO { return new AirFilterO. }
}
}
class CabinAirFiIter extends Filter { public static class Factory
implements typeinfo factory Factory<CabinAirFilter> { public CabinAi rFi Iter createO {
return new CabinAirFilter();
}
class Oil Filter extends Filter { public static class Factory implements typeinfo factory Factory<OilFilter> {
public Oil Filter createO { return new OilFilterO; }
}
}
class Belt extends Part {}
class FanBelt extends Belt {
public static class Factory
implements typeinfo.factory.Factory<FanBelt> {
public FanBelt createO { return new FanBeltO; }
}
}
class GeneratorBelt extends Belt { public static class Factory
implements typeinfo.factory.Factory<GeneratorBelt> { public GeneratorBelt createO {
return new GeneratorBeltO:
}
class PowerSteeringBelt extends Belt { public static class Factory
implements typei nfо.factory.Factory<PowerSteeri ngBelt> { public PowerSteeringBelt createO {
return new PowerSteeringBeltO;
}
}
}
public class RegisteredFactories {
public static void main(String[] args) { for(int i = 0; i < 10; i++)
System.out.pri ntin(Part.createRandom());
}
} /* Output: GeneratorBelt CabinAirFiIter GeneratorBelt AirFiIter PowerSteeringBelt CabinAirFiIter Fuel Filter PowerSteeringBelt PowerSteeringBelt Fuel Filter *///:-
He все классы иерархии рассчитаны на создание экземпляров; в нашем примере классы Filter и Belt существуют исключительно в целях классификации. Экземпляры этих классов не создаются — только одного из их субклассов. Если класс должен создаваться посредством createRandom(), он содержит внутренний класс Factory.
Хотя для включения всех фабрик в список можно воспользоваться вызовом Collections.addAll(), компилятор выдает предупреждение, поэтому я вернулся к вызовам add(). Метод createRandom() случайным образом выбирает объект фабрики из partFactories и вызывает его метод create() для получения нового объекта Part.
instanceof и сравнение Class
При получении информации о типе объекта важно различать действие любой формы оператора instanceof (будь это сам оператор instanceof или метод isInstanceO ~~ они дают одинаковые результаты) и прямого сравнения объектов Class. Вот пример, который показывает, в чем их различия:
//. typeinfo/FamilyVsExactType java // Различия между instanceof и class package typeinfo,
import static net mindview util.Print.*, class Base {}
class Derived extends Base {}
public class FamilyVsExactType { static void test(Object x) {
print ("Тестируем x типа " + x.getClassO); printC'x instanceof Base " + (x instanceof Base)), printC'x instanceof Derived "+ (x instanceof Derived)); print("Base.isInstance(x) "+ Base.class.islnstance(x)); print("Derived islnstance(x) " +
Deri ved.class.i slnstance(x)); printC'x getClassO == Base.class " +
(x.getClassO == Base.class)); printC'x.getClassO == Derived.class " +
(x.getClassO == Deri ved. cl ass)). print("x.getClassO.equals(Base.class)) "+
(x getClassO .equals(Base.class))); printC'x getClassO equals (Deri ved. class)) " + (x.getClassO. equals (Deri ved. class)));
}
public static void main(String[] args) { test(new BaseO); test (new DerivedO),
}
} /* Output:
Тестируем x типа class typeinfo.Base x instanceof Base true x instanceof Derived false Base islnstance(x) true Derived islnstance(x) false x getClassO == Base.class true x getClassO == Derived class false x getClassO equals(Base.class)) true x.getClassO equals(Derived.class)) false Тестируем x типа class typeinfo.Derived x instanceof Base true x instanceof Derived true Base.islnstance(x) true Derived.islnstance(x) true x.getClassO == Base.class false x.getClassO == Derived class true x.getClassO equals(Base.class)) false x.getClassO.equals(Derived.class)) true *///:-
Метод test() осуществляет проверку типов полученного объекта, используя для этого обе формы оператора instanceof. Затем он получает ссылку на объект Class и использует операцию сравнения ссылок == и метод equals(), чтобы проверить объекты Class на эквивалентность. Пример доказывает справедливость утверждения о том, что действие оператора instanceof и метода islnstance() одинаково. Совпадают и результаты работы операции сравнения == и метода equals(). Но сами тесты приводят к разным заключениям. В соответствии с концепцией типа instanceof дает ответ на вопрос: «Объект принадлежит этому классу или производному от него?» С другой стороны, сравнение объектов Class оператором == не затрагивает наследования — либо тип точно совпадает, либо нет.
Рефлексия: динамическая информация о классе
Если вы не знаете точный тип объекта, RTTI сообщит вам его. Однако в этом случае существуют ограничения: тип должен быть известен еще во время компиляции программы, иначе определить его с помощью RTTI и сделать с этой информацией что-то полезное будет невозможно. Другими словами, компилятор должен располагать информацией обо всех классах, к которым вы затем хотели бы применить динамическое определение типов (RTTI).
Сначала кажется, что это ограничение не столь существенно, но предположим, что у вас появилась ссылка на объект, который не находится в пространстве вашей программы. Более того, класс этого объекта недоступен во время ее компиляции. Например, вы получили последовательность байтов с диска или из сетевого соединения, и вам сказали, что эта последовательность представляет некоторый класс. Но компилятор ничего не знал об этом классе, когда обрабатывал вашу программу, как же его можно использовать?
В традиционных средах программирования такая задача показалась бы далекой от реальности. Однако границы мира программирования все больше расширяются и мы все чаще встречаемся с такими ситуациями. Во-первых, такие возможности требуются для компонентного программирования, которое служит основой для систем быстрой разработки приложений (Rapid Application Development, RAD). Это визуальный подход для создания программ (экран представлен в виде «формы»), где значки, представляющие визуальные компоненты, перетаскиваются на форму. Затем происходит настройка этих компонентов, они устанавливаются в некоторое состояние во время работы программы. Чтобы изменить состояние компонентов, необходимо некоторым образом создавать их экземпляры, просматривать их содержимое, считывать и записывать внутренние значения. Вдобавок компоненты с поддержкой событий графического интерфейса должны как-то рассказать о них, чтобы система быстрой разработки приложений помогла программисту реализовать поддержку этих событий. Механизм рефлексии предоставляет средства для получения информации о доступных методах и их именах. Такое компонентное программирование поддерживается и в Java, с помощью технологии JavaBeans.
Другая важная предпосылка поддержки динамической информации о классе — предоставление возможности создавать и использовать объекты на удаленных платформах. Этот механизм, называемый удаленным вызовом методов (Remote Method Invocation, RMI), позволяет программе на Java распределять свои объекты по нескольким машинам. Необходимость в удаленном вызове методов возникает по разным причинам: например, при выполнении задачи с интенсивными вычислениями можно сбалансировать нагрузку по доступным компьютерам. Иногда код, выполняющий определенные операции, размещается на одной машине, чтобы она стала общим хранилищем этих операций и любые изменения кода на такой машине автоматически распространялись на всех клиентов этого кода. (Интересный поворот — компьютер существует исключительно для того, чтобы упростить внесение изменений в программное обеспечение!) Ко всему прочему распределенное программирование также поддерживает удаленное специализированное оборудование, которое эффективно выполняет некоторые задачи — например, обращение матриц, — которые при решении их на локальной машине могут потребовать слишком много времени и ресурсов.