Абстрагирование использования


С созданием мы разобрались, теперь пришло время заняться дизайном оставшейся части: где используются классы. Так как здесь происходит сортировка по корзинам, которая особенно громоздкая и бросающаяся в глаза, почему бы ни взять этот процесс и не спрятать его внутри класса? В этом заключен принцип "Если вы должны сделать что-то громоздкое, попробуйте локализовать громоздкость внутри класса". Это выглядит так:

Инициализация объекта TrashSorter теперь должна меняться при добавлении нового типа Мусора в модель. Вы должны представить, что класс TrashSorter должен выглядеть примерно так:

class TrashSorter extends ArrayList {
  
void sort(Trash t) { /* ... */
  
}
}

Таким образом, TrashSorter является списком (ArrayList) списка ссылок (ArrayList) Мусора, и с помощью add( ) вы можете установить еще один, например:

TrashSorter ts = new TrashSorter();
ts.add
(new ArrayList());

Однако теперь сортировка становится проблемой. Как заставить статически закодированный метод работать притом, что могут быть добавлены новые типы? Чтобы решить эту проблему информация о типах должна быть удалена из метода sort( ), чтобы все, что нужно сделать, заключалось в вызове общего метода, который заботился бы о детализации по типу. В этом, конечно, заключается другой метод описания динамического построения метода. Таким образом sort( ) будет просто проходить по последовательности и вызывать динамически построенный метод для каждого списка (ArrayList). Так как задачей этого метода является сборка кусков мусора, в которых он заинтересован, он вызовет метод garb(Trash). Структура выглядит примерно так:

TrashSorter'у нужно вызывать каждый метод grab( ) и получать различные результаты в зависимости от типа Мусора, содержащегося в ArrayList. То есть, каждый список должен осознавать, какой тип он содержит. Классический подход к этой проблеме состоит в создании базового класса "Мусорная корзина" и наследовании от него новых классов для каждого типа, который вы хотите собирать. Если бы в Java был механизм параметризированных типов, это был бы наиболее подходящий способ. Нам не нужно было бы вручную кодировать все классы, этот механизм мог бы построить все за нас. Дальнейшее рассмотрение поможет нам выработать лучший подход.

Основной принцип дизайна ООП состоит в "Использовании членов-данных для различения состояний, использовании полиморфизма для различия в поведении". Первой мыслью, которая может возникнуть у вас, может быть та, что метод grab( ) конечно же ведет себя по-разному в зависимости того, содержится в списке (ArrayList) Бумага или Стекло. Но что этот метод делает, строго определяется типом и ничем другим. Это может быть интерпретировано, как различные состояния, а так как в Java существует класс для представления типа (Class), это можно использовать для определения типа Мусора, при помещении его в определенную корзину Tbin.

Конструктор для такого Tbin ожидает, что вы передадите ему Class по вашему выбору. Это сообщит списку, какой тип ему предназначено хранить. Затем метод grab( ) использует Class BinType и RTTI, чтобы проверить, что объект Мусора, который вы держите, соответствует типу, который поддерживает корзина.

Вот новая версия программы:

//: refactor:recycleb:RecycleB.java
// Контейнеры, которые собирают объекты по интересам.
package refactor.recycleb;

import refactor.trash.*;

import java.util.*;

import junit.framework.*;

// Контейнер, который допускает только правильный тип
// Мусора (устанавливается в конструкторе):
class Tbin {
  
private List list = new ArrayList();
  
private Class type;
  
  
public Tbin(Class binType) {
     
type = binType;
  
}
  
  
public boolean grab(Trash t) {
     
// Кроверяем тип класса:
     
if (t.getClass().equals(type)) {
        
list.add(t);
        
return true; // Объект собран
     
}
     
return false; // Объект не собран
  
}
  
  
public Iterator iterator() {
     
return list.iterator();
  
}
}

class TbinList extends ArrayList {
  
void sortTrashItem(Trash t) {
     
Iterator e = iterator(); // Двигается по себе
     
while (e.hasNext())
        
if (((Tbin) e.next()).grab(t))
           
return;
     
// Тужна новая корзина(Tbin) для этого типа:
     
add(new Tbin(t.getClass()));
      sortTrashItem
(t); // Рекурсивный вызов
  
}
}

public class RecycleB extends TestCase {
  
Collection bin = new ArrayList();
   TbinList trashBins =
new TbinList();
  
  
public RecycleB() {
     
ParseTrash.fillBin("../trash/Trash.dat", bin);
  
}
  
  
public void test() {
     
Iterator it = bin.iterator();
     
while (it.hasNext())
        
trashBins.sortTrashItem((Trash) it.next());
      Iterator e = trashBins.iterator
();
     
while (e.hasNext())
        
Trash.sumValue(((Tbin) e.next()).iterator());
      Trash.sumValue
(bin.iterator());
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(RecycleB.class);
  
}
}
// /:~

Tbin содержит ссылку type на Class для того типа, который устанавливается в конструкторе и который должен собираться. Метод grab( ) сравнивает этот тип с типом того объекта, который вы передали. Обратите внимание, что в этом дизайне grab( ) принимает только объекты типа Trash, так что вы получите проверку базового типа во время компиляции, но вы также можете принимать просто Object и это также будет работать.

TTbinList содержит множество ссылок на Tbin, так что метод sort( ) может перебирать Tbin для поиска корзины, содержащей объекты Мусора. Если совпадение не найдено, то он создает новую корзину Tbin для того типа, который не был найден, и делает рекурсивный вызов самого себя - при следующем проходе корзина нового типа будет найдена.

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