RTTI относительно полезно?


Различные дизайны этой главы позволяли удалить RTTI, который может произвести впечатления, что оно "относительно полезно" (возражения, используемые для плохого, зловредного goto, который не был внесен в Java). Это не так. Злоупотребление RTTI является проблемой. Причина, по которой мы удалили RTTI из нашего дизайна, состоит в том, что злоупотребление этой возможностью делает невозможным расширение, а основной целью было предоставление возможности добавление новых типов в систему с минимальным изменением окружающего кода. Так как люди часто злоупотребляют RTTI, рассматривая каждый тип в вашей системе, это является причиной не расширяемости кода: когда вы добавляете новый тип, вы начинаете охотиться за всеми местами в коде, где используется RTTI, и если вы пропустите что-то, вы не получите помощь от компилятора.

Однако использование RTTI не означает, что автоматически получается не расширяемый код. Давайте взглянем еще раз на переработчик мусора. В этот раз будет введен новый инструмент, который я назвал TypeMap. Он содержит HashMap, которая хранит списки ArrayList, но имеет простой интерфейс: вы можете добавить (add( )) новый объект и вы можете получить (get( )) список (ArrayList), содержащий все объекты определенного типа. Ключами для HashMap являются типы из ассоциированного списка (ArrayList). Красота этого дизайна состоит в том, что TypeMap динамически добавляет новые пары в любое время, когда он обнаруживает новые типы, так что когда бы вы ни добавили новый тип в систему (даже если добавление происходит во время выполнения), он адаптируется.

Наш пример опять таки будет построен на структуре типов Мусора из пакета refractor.Trash (также используется файл Trash.dat без изменений):

//: refactor:dynatrash:DynaTrash.java
// Использование Карты Списков и RTTI для автоматической сортировки
// мусора в ArrayLists. Решение, несмотря на использование RTTI,
// является гибким.
package refactor.dynatrash;

import refactor.trash.*;

import java.util.*;

import junit.framework.*;

// Основные типы TypeMap работают в любой ситуации:
class TypeMap {
  
private Map t = new HashMap();
  
  
public void add(Object o) {
     
Class type = o.getClass();
     
if (t.containsKey(type))
         ((
List) t.get(type)).add(o);
     
else {
        
List v = new ArrayList();
         v.add
(o);
         t.put
(type, v);
     
}
   }
  
  
public List get(Class type) {
     
return (List) t.get(type);
  
}
  
  
public Iterator keys() {
     
return t.keySet().iterator();
  
}
}

// Класс адаптера для обратного вызова из
// ParseTrash.fillBin():
class TypeMapAdapter implements Fillable {
  
TypeMap map;
  
  
public TypeMapAdapter(TypeMap tm) {
     
map = tm;
  
}
  
  
public void addTrash(Trash t) {
     
map.add(t);
  
}
}

public class DynaTrash extends TestCase {
  
TypeMap bin = new TypeMap();
  
  
public DynaTrash() {
     
ParseTrash.fillBin("../trash/Trash.dat", new TypeMapAdapter(bin));
  
}
  
  
public void test() {
     
Iterator keys = bin.keys();
     
while (keys.hasNext())
        
Trash.sumValue(bin.get((Class) keys.next()).iterator());
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(DynaTrash.class);
  
}
}
// /:~

Несмотря на силу, определение TypeMap достаточно просто. Здесь содержится HashMap и метода add( ), который делает основную работу. Когда вы добавляете новый объект, получается ссылка на объект типа Class для этого типа. Она используется в качестве ключа для определения содержит ли какой-либо из ArrayList объекты этого типа в HashMap. Если это так, то мы получаем соответствующий ArrayList и добавляем в него объект. Если нет, объект Class и новый ArrayList добавляются в качестве пары ключ-значение.

Вы можете получить Итератор для всех объектов типа Class из keys( ) и использовать каждый объект Class для получения соответствующего списка (ArrayList) с помощью get( ). Вот и все, что здесь сделано.

Метод filler( ) интересен, поскольку он развивает дизайн ParseTrash.fillBin( ), в результате чего метод не просто заполняет список (ArrayList), а вместо этого он реализует интерфейс Fillable с его методом addTrash( ). Все, что нужно сделать заполнителю filler( ), это вернуть ссылку на интерфейс, который реализует Fillable, а затем эта ссылка может быть использована в качестве аргумента метода fillBin( ) следующим образом:

ParseTrash.fillBin("Trash.dat", bin.filler());

 
Чтобы воспроизвести такую ссылку используется анонимный внутренний класс (описано в главе 8 Thinking in Java, 2nd edition). Вам никогда не понадобится именованный класс, чтобы реализовать интерфейс Fillable, вам просто нужна ссылка на объект этого класса, так что это подходящее использование анонимного внутреннего класса.

Интересным в этом дизайне является то, что хотя в задумках он создавался так, чтобы не заботится о сортировке, fillBin( ) выполняет сортировку при каждой вставке объекта Мусора в корзину.

Многое из класса DynaTrash может походить на предыдущий пример. В то же время, вместо помещения нового объекта Мусора в корзину типа списка (ArrayList) корзина является типа TypeMap, так что когда мусора помещен в корзину, он моментально сортируется внутренним механизмом TypeMap. Перебор TypeMap и операции над каждым индивидуальным списком (ArrayList) происходят аналогичным образом.

Как вы можете видеть, добавление нового типа в систему совсем не повлияет на этот код, а код в TypeMap полностью независим. Это, конечно, небольшое решение проблемы, и, бесспорно, более элегантное. Оно полностью завязано на RTTI, но обратите внимание, что каждая пара ключ-значение в HashMap проверяется только на один тип. Кроме того, здесь нет возможности "забыть" добавить правильный код в систему, когда вы добавляете новый тип, так как здесь нет какого-либо кода, который нужно было бы добавлять.