Решения в книге "Design Patterns" организованы вокруг вопроса "Что изменится при эволюции этой программы?" Обычно это наиболее важный вопрос, который вы можете задать при любом дизайне. Если вы хотите построить вашу систему вокруг ответа, результат может быть двойственный: ваша система не только позволит легкий (и дешевый) уход, но вы можете также произвести компоненты, которые можно повторно использовать, так что другие системы могут быть построены с меньшими затратами. Это обещает объектно-ориентированное программирование, но это не происходит автоматически. Для этого требуется думать и понимать вашу часть. В этом разделе мы посмотрит на то, как происходит этот процесс во время детализации системы.
Ответ на вопрос "Что измениться?" для перерабатывающей системы единственный: можно добавить больше типов в систему. Цель дизайна состоит в том, чтобы сделать это добавление типов настолько безболезненным, насколько это возможно. В перерабатывающей программе мы хотим инкапсулировать все части, в которых указывается информация о типе, как упоминается, так чтобы (если нет других причин) любые изменения могли быть локализованы в этой инкапсуляции. Это заставляет нас активизировать процесс по очистке остального кода.
"Создание большего количества объектов"
Здесь проявляется главный принцип объектно-ориентированного дизайна, о котором я впервые говорил с Грэди Бучем (Grady Booch): "Если дизайн чрезмерно сложный, сделайте больше объектов". Это одновременно является и противоречащим интуиции, и простым до нелепости, и все же - это наиболее полезный совет, который я нашел. (Вы можете заметить, что "создание большего числа объектов" часто эквивалентно "добавлению другого уровня обходных путей".) В общем, если вы нашли место с беспорядочным кодом, решите, класс какого рода мог бы очистить его. Часто второстепенным эффектом очистки является то, что система становится лучше структурированной и более гибкой.
Рассмотрим первое место, в котором создаются объекты Мусора - это выражение switch внутри main( ):
for(int i = 0; i < 30; i++)
switch((int)(rand.nextInt(3)) {
case 0 :
bin.add(new
Aluminum(rand.nextDouble() * 100));
break;
case 1 :
bin.add(new
Paper(rand.nextDouble() * 100));
break;
case 2 :
bin.add(new
Glass(rand.nextDouble() * 100));
}
Это точно беспорядочный код, а также место, которое вы обязаны менять, когда добавляете новый тип. Если добавление новых типов - это обычное дело, лучшим решением будет единственный метод, который принимает всю необходимую информацию и производит ссылку на объект правильного типа, уже приведенного к базовому классу объекта мусора. В книге "Design Patterns" это названо, как шаблон создания (которых здесь несколько). Специальный шаблон, который применен здесь, является вариацией Метода Фабрики. В данном случае метод фабрики является статическим членом класса Trash, но в более общем случае этот метод является перегруженным в наследуемом классе.
Идея метода фабрики состоит в том, что вы передаете наиважнейшую информацию, которую нужно знать для создания вашего объекта, а затем просто ждете получения ссылки (уже приведенной к базовому типу), которая будет получена в качестве возвращаемого значения. С этого момента вы рассматриваете объект полиморфически. Таким образом, вам не нужно знать точного типа объекта, который уже создан. Фактически, метод фабрики прячет его от вас, предотвращая неверное употребление. Если вы хотите использовать объект без полиморфизма, вы должны явно использовать RTTI и приведение типов.
Но здесь есть небольшая проблема, особенно когда вы используете более сложный подход (не покорзинный здесь) создания метода фабрики в базовом классе и перегрузке его в наследуемых классах. Что если требуемая информация в наследуемом классе включает больше аргументов или отличающиеся аргументы? Создание большего количества объектов - решает эту проблему. Для реализации метода фабрики класс Trash получает новый метод с названием factory. Чтобы спрятать создающие данные, возьмем новый класс, с названием Messenger, который несет всю необходимую информацию методу factory для создания соответствующего объекта Мусора (мы начинали упоминать Посыльного (Messenger), как о шаблоне проектирования, но это достаточно просто, так что вы можете не придавать ему этот статус). Вот простейшая реализация Messenger:
class Messenger {
int type;
// Должны изменить это для
добавления другого типа:
static final int MAX_NUM = 4;
double data;
Messenger(int typeNum, double val) {
type = typeNum % MAX_NUM;
data = val;
}
}
Работа объекта Messenger состоит только в содержании информации для метода factory( ). Теперь, если есть ситуация, в котором методу factory( ) необходимо больше информации или информация другого рода для создания нового типа объекта Мусора, нет необходимости в изменении интерфейса factory( ). Класс Messenger может быть изменен путем добавления новых данных и новых конструкторов или более типичным способом для объектно-ориентированного подхода - путем создания подклассов.
Метод factory( ) для этого примера работает следующим образом:
static Trash factory(Messenger i) {
switch (i.type) {
default: // Чтобы успокоить компилятор
case 0:
return new Aluminum(i.data);
case 1:
return new Paper(i.data);
case 2:
return new Glass(i.data);
// :
case 3:
return new Cardboard(i.data);
}
}
Таким образом, определение точного типа объекта достаточно просто, но вы можете представить более сложные системы, в которых factory( ) использует более сложный алгоритм. Теперь это спрятано в одном месте, и вы знаете, куда нужно обратится, чтобы добавить новый тип.
Создание новых объектов теперь упростится в main( ):
for(int i = 0; i < 30; i++)
bin.add(
Trash.factory(
new Messenger(
rand.nextInt(Messenger.MAX_NUM),
rand.nextDouble() * 100)));
Теперь создается объект Messenger для передачи данных в метод factory( ), которые необходимы для создание некоторого объекта Мусора в куче и возврата ссылки, которая добавляется в корзину ArrayList bin. Конечно, если вы измените количество и тип аргументов, это выражение опять должно быть изменено, но изменений будет меньше, если создание объекта Messenger будет автоматизировано. Например, в конструктор Messenger может быть передан список (ArryaList) аргументов (или напрямую в вызов метода factory( ), в сущности). Поэтому требуется, чтобы аргументы анализировались и проверялись в момент выполнения, что предоставит великолепную гибкость.
Из этого кода видно, что "вектор изменений" проблемы фабрики ответственен за решение: если вы добавляете новые типы в систему (изменение), вы должны изменить код только внутри фабрики, так что фабрика изолирует эффект изменений.
← | Симулятор переработки мусора | Шаблон прототипного создания | → |