Симулятор переработки мусора


Природа этой проблемы состоит в том, что мусор выбрасывается без сортировки в единую корзину, так что специфичная часть информации теряется. Но позже специфическая информация может быть восстановлена для достижения правильной сортировки мусора. В качестве начального решения используем RTTI (описано в 12 главе Thinking in Java, 2nd edition).

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

//: refactor:recyclea:RecycleA.java
// Переработка с RTTI.
package refactor.recyclea;

import java.util.*;

import java.io.*;

import junit.framework.*;

abstract class Trash {
  
private double weight;
  
   Trash
(double wt) {
     
weight = wt;
  
}
  
  
abstract double getValue();
  
  
double getWeight() {
     
return weight;
  
}
  
  
// Суммируем количество Мусора в корзине:
  
static void sumValue(Iterator it) {
     
double val = 0.0f;
     
while (it.hasNext()) {
        
// Первый вид RTTI:
         // Проверка динамическим приведением
        
Trash t = (Trash) it.next();
        
// Полиморфизм в действии:
        
val += t.getWeight() * t.getValue();
         System.out.println
("weight of " +
        
// Используем RTTI для получения типовой
               // информации о классе:
              
t.getClass().getName() + " = " + t.getWeight());
     
}
     
System.out.println("Total value = " + val);
  
}
}

class Aluminum extends Trash {
  
static double val = 1.67f;
  
   Aluminum
(double wt) {
     
super(wt);
  
}
  
  
double getValue() {
     
return val;
  
}
  
  
static void setValue(double newval) {
     
val = newval;
  
}
}

class Paper extends Trash {
  
static double val = 0.10f;
  
   Paper
(double wt) {
     
super(wt);
  
}
  
  
double getValue() {
     
return val;
  
}
  
  
static void setValue(double newval) {
     
val = newval;
  
}
}

class Glass extends Trash {
  
static double val = 0.23f;
  
   Glass
(double wt) {
     
super(wt);
  
}
  
  
double getValue() {
     
return val;
  
}
  
  
static void setValue(double newval) {
     
val = newval;
  
}
}

public class RecycleA extends TestCase {
  
Collection bin = new ArrayList(), glassBin = new ArrayList(),
         paperBin =
new ArrayList(), alBin = new ArrayList();
  
private static Random rand = new Random();
  
  
public RecycleA() {
     
// Наполним Мусором корзину:
     
for (int i = 0; i < 30; i++)
        
switch (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));
        
}
   }
  
  
public void test() {
     
Iterator sorter = bin.iterator();
     
// Отсортируем Мусор:
     
while (sorter.hasNext()) {
        
Object t = sorter.next();
        
// RTTI покажет членство в классах:
        
if (t instanceof Aluminum)
           
alBin.add(t);
        
if (t instanceof Paper)
           
paperBin.add(t);
        
if (t instanceof Glass)
           
glassBin.add(t);
     
}
     
Trash.sumValue(alBin.iterator());
      Trash.sumValue
(paperBin.iterator());
      Trash.sumValue
(glassBin.iterator());
      Trash.sumValue
(bin.iterator());
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(RecycleA.class);
  
}
}
// /:~

В исходном коде для этой книги этот файл будет помещен в поддиректорий recyclea, который не включен в поддиректорий refractor. Инструмент распаковки позаботится о помещении его в правильный директорий. Причина такого размещения состоит в том, что глава переписывает этот пример несколько раз и помещает каждую версию в свой собственный директорий (с использованием пакета по умолчанию в каждом директории, чтобы облегчить работу программы), так что имена классов не конфликтуют.

Несколько объектов ArrayList создаются для хранения Мусор (Trash). Конечно, ArrayList содержит Object, так что они могут содержать все, что угодно. Причина для того, чтобы они хранили Мусор (Trash) состоит в том, чтобы вы были осторожны и не положили ничего, кроме Trash. Если вы поместите что-то "неправильное" в ArrayList, вы не получите никаких предупреждений или ошибок во время компиляции - вы узнаете об этом только через исключение во время выполнения.

Когда добавляется ссылка Trash, она теряет свою специфическую идентичность и становится просто ссылкой на Object (над ней происходит восходящее приведение). Однако по причине полиморфизма происходит правильное течение программы, когда вызывается метод динамического ограничения посредством сортирующего Итератора, в котором результирующий Object будет приведен назад к типу Trash. sumValue( ) также получает Iterator для выполнения операций над каждым объектом из ArrayList.

Это выглядит как простое преобразование типа Trash в контейнере, содержащем базовый тип, и обратное приведение типа. Почему бы ни поместить мусор в соответствующий приемник сразу? (Неужели в этом вся загадка переработки). В такой программе было бы легче перерабатывать, но иногда структура системы и гибкость могут принести выгоду от приведения к базовому классу.

Программа удовлетворяет требованиям дизайна: она работает. Это может быть здорово, если это разовое решение. Однако полезные программы имеют тенденцию эволюционировать со временем, так что вы должны спросить: "Что, если ситуация изменится?" Например, если картон станет допустимым к обработке, то как эту возможность можно интегрировать в систему (особенно, если программа большая и сложная). Так как приведенный выше код основывается на выражении switch, которое может быть размыто по всей программе, вы должны просматривать весь код каждый раз, когда добавляется новый тип, и если вы пропустите одном из мест, компилятор не даст вам никакой помощи, указав на ошибку.

Ключевой момент неправильного использования RTTI в данном случае заключается в том, что проверяется каждый тип. Если вы ищите только определенное подмножество типов, поскольку это подмножество имеет специальную обработку, то возможно это хорошо. Но если вы охотитесь за каждым типом внутри выражения switch, то вы, вероятно, упускаете важный момент, и определенно делаете ваш код более трудным в уходе. В следующем разделе мы взглянем на то, как эта программа эволюционирует через несколько этапов и станет более гибкой. Это должно показать значимый пример дизайна программ.