46

46


//: innerclasses/control 1er/Control1er.java // Обобщенная система управления package innerclasses.controller; import java.util.*;
public class Controller {
// Класс из пакета java.util для хранения событий Event: private List<Event> eventList = new ArrayList<Event>(); public void addEvent(Event c) { eventList.add(c): } public void run() {
while(eventList.size() > 0) {
for(Event e : new ArrayList<Event>(eventList)) if(e.readyO) {
System.out.println(e): e.actionO; eventList.remove(e):
}
}
} ///:-
Метод run() в цикле перебирает копию eventList в поисках событий Event, го­товых для выполнения. Для каждого найденного элемента он выводит инфор­мацию об объекте методом toString(), вызывает метод action(), а после этого уда­ляет событие из списка.
Заметьте, что в этой архитектуре совершенно неважно,чтоконкретно вы­полняет некое событие Event. В этом и состоит «изюминка» разработанной сис­темы; она отделяет постоянную составляющую от изменяющейся. «Вектором изменения» являются различные действия разнообразных событий Event, выра­жаемые посредством создания разных субклассов Event.
На этом этапе в дело вступают внутренние классы. Они позволяют добиться двух целей:
1.     Вся реализация системы управления создается в одном классе, с полной инкапсуляцией всей специфики данной реализации. Внутренние классы используются для представления различных разновидностей action(), не­обходимых для решения задачи.
2.     Внутренние классы помогают избежать громоздкой, неудобной реализа­ции, так как у них есть доступ к внешнему классу. Без этой возможности программный код очень быстро станет настолько неприятным, что вам захочется поискать другие альтернативы.
Рассмотрим конкретную реализацию системы управления, разработанную для управления функциями оранжереи
[3]
. Все события — включение света, воды и нагревателей, звонок и перезапуск системы — абсолютно разнородны. Однако система управления разработана так, что различия в коде легко изолируются. Внутренние классы помогают унаследовать несколько производных версий од­ного базового класса Event в пределах одного класса. Для каждого типа события от Event наследуется новый внутренний класс, и в его реализации action() запи­сывается управляющий код.
Как это обычно бывает при использовании каркасов приложений, класс GreenhouseControls наследует от класса Controller:

//: innerclasses/GreenhouseControls.java // Пример конкретного приложения на основе системы // управления, все находится в одном классе. Внутренние // классы дают возможность инкапсулировать различную // функциональность для каждого отдельного события, import innerclasses.control 1er.*,
public class GreenhouseControls extends Controller {
private boolean light = false,
public class LightOn extends Event {
public LightOndong delayTime) { super (delayTime). } public void actionO {
// Сюда помещается аппаратный вызов, выполняющий // физическое включение света, light = true;
}
public String toStringO { return "Свет включен"; }
}
public class LightOff extends Event {
public LightOffdong delayTime) { super(delayTime); } public void actionO {
// Сюда помещается аппаратный вызов, выполняющий // физическое выключение света light = false;
}
public String toStringO { return "Свет выключен", }
}
private boolean water = false;
public class WaterOn extends Event {
public WaterOn(long delayTime) { super(delayTime), } public void actionO {
// Здесь размещается код включения '// системы полива, water = true;
}
public String toStringO {
return "Полив включен";
}
}
public class WaterOff extends Event {
public WaterOffdong delayTime) { super(delayTime); } public void actionO {
// Здесь размещается код выключения // системы полива water = false;
}
public String toStringO {
return "Полив отключен";
}
}
private String thermostat = "День";
public class Thermostaticght extends Event {
public Thermostaticght(long delayTime) { super(delayTime);
}
public void actionO {
// Здесь размещается код управления оборудованием thermostat = "Ночь";

public String toStringO {
return "Термостат использует ночной режим";
}
}
public class ThermostatDay extends Event {
public ThermostatDay(long delayTime) { super(delayTime);
}
public void actionO {
// Здесь размещается код управления оборудованием thermostat = "День";
}
public String toStringO {
return "Термостат использует дневной режим";
}
}
// Пример метода actionO, вставляющего
// самого себя в список событий.
public class Bell extends Event {
public Bell(long delayTime) { super(delayTime); } public void actionO {
addEvent(new Bell(delayTime));
}
public String toStringO { return "Бам!"; }
}
public class Restart extends Event { private Event[] eventList;
public Restartdong delayTime. Event[] eventList) { super(delayTime); this.eventList = eventList; for(Event e : eventList) addEvent(e);
}
public void actionO {
for(Event e : eventList) {
e. start О; // Перезапуск каждый раз addEvent(e);
}
startO; // Возвращаем это событие Event addEvent(this);
}
public String toStringO {
return "Перезапуск системы";
}
}
public static class Terminate extends Event {
public Terminatedong delayTime) { super(delayTime); }
public void actionO { System.exit(0); }
public String toStringO { return "Отключение"; }
}
} ///;-
Заметьте, что поля light, thermostat и ring принадлежат внешнему классу GreenhouseControls, и все же внутренние классы имеют возможность обращаться к ним, не используя особой записи и не запрашивая особых разрешений. Боль­шинство методов action() требует управления оборудованием оранжереи, что, скорее всего, привлечет в программу сторонние низкоуровневые вызовы.
В основном классы Event похожи друг на друга, однако классы Bell и Restart представляют собой особые случаи. Bell выдает звуковой сигнал и добавляет себя в список событий, чтобы звонок позднее сработал снова. Заметьте, что внутренние классы действуютпочтикак множественное наследование: классы Bell и Restart имеют доступ ко всем методам класса Event, а также ко всем мето­дам внешнего класса GreenhouseControls.
Классу Restart передается массив объектов Event, которые он добавляет в контроллер. Так как Restart также является объектом Event, вы можете доба­вить этот объект в список событий в методе Restart.action(), чтобы система регу­лярно перезапускалась.
Следующий класс настраивает систему, создавая объект GreenhouseControls и добавляя в него разнообразные типы объектов Event. Это пример шаблона проектирования «команда»— каждый объект в EventList представляет собой за­прос, инкапсулированный в объекте:
//: с08:GreenhouseControl1er.java // Настраивает и запускает систему управления. // {Args: 5000}
import innerclasses.control 1er.*:
public class GreenhouseController {
public static void main(String[] args) {
GreenhouseControls gc = new GreenhouseControls(); // Вместо жесткого кодирования фиксированных данных // можно было бы считать информацию для настройки // из текстового файла: gc.addEvent(gc.new Bel 1 (900)): Event[] eventList = {
gc.new ThermostatNight(O), gc.new Light0n(200), gc.new LightOff(400), gc.new WaterOn(600), gc.new WaterOff(800), gc.new ThermostatDay(1400)
}:
gc.addEvent(gc.new Restart(2000, eventList)): if(args.length == 1) gc.addEvent(
new GreenHouseControls.Terminate( new Integer(args[0])));
gc.runO;
}
} * Output: Вам!
Термостат использует ночной режим
Свет включен
Свет выключен
Полив включен
Полив отключен
Термостат использует дневной режим Перезапуск системы Отключение ///:-
Класс инициализирует систему, включая в нее нужные события. Если пере­дать программе параметр командной строки, она завершается по истечении за­данного количества миллисекунд (используется при тестировании). Конечно, чтобы программа стала более гибкой, описания событий следовало бы не вклю­чать в программный код, а загружать из файла.
Этот пример поможет понять всю ценность механизма внутренних классов, особенно в случае с системами управления.
Наследование от внутренних классов
Так как конструктор внутреннего класса связывается со ссылкой на окружаю­щий внешний объект, наследование от внутреннего класса получается чуть сложнее, чем обычное. Проблема состоит в том, что «скрытая» ссылка на объ­ект объемлющего внешнего классадолжна бытьинициализирована, а в произ­водном классе больше не существует объемлющего объекта по умолчанию. Для явного указания объемлющего внешнего объекта применяется специальный синтаксис:
//: innerclasses/Inheritlnner.java // Наследование от внутреннего класса.
class Withinner { class Inner {}
}
public class Inheritlnner extends Withlnner.Inner { //! InheritlnnerO {} // He компилируется InheritInner(WithInner wi) { wi.super();
}
public static void main(String[] args) { Withlnner wi = new WithlnnerO; Inheritlnner ii = new Inheritlnner(wi);
}
} ///:-
Здесь класс Inheritlnner расширяет только внутренний класс, а не внешний. Но когда дело доходит до создания конструктора, предлагаемый по умолчанию конструктор не подходит, и вы не можете просто передать ссылку на внешний объект. Необходимо включить в тело конструктора выражение
ссылкаНаОбъемлющийКласс.super();
в теле конструктора. Оно обеспечит недостающую ссылку, и программа отком- пилируется.
Можно ли переопределить внутренний класс?
Что происходит, если вы создаете внутренний класс, затем наследуете от его внеш­него класса, а после этого заново описываете внутренний класс в производном

Можно ли переопределить внутренний класс?273классе? Другими словами, можно ли переопределить внутренний класс? Это было бы довольно интересно, но «переопределение» внутреннего класса, как если бы он был еще одним методом внешнего класса, фактически не имеет ни­какого эффекта:
//. innerclasses/BigEgg.java // Внутренний класс нельзя переопределить // подобно обычному методу, import static net.mindview util.Print.*:
class Egg {
private Yolk y; protected class Yolk {
public YolkO { printCEgg.YolkO"). }
}
public EggO {
printC'New EggO"): у = new YolkO:



public class BigEgg extends Egg { public class Yolk {
public YolkO { print("BigEgg YolkO"): }
}
public static void main(String[] args) { new BigEggO;
}
} /* Output New EggO Egg. YolkO *///•-
Конструктор по умолчанию автоматически синтезируется компилятором, а в нем вызывается конструктор по умолчанию из базового класса. Можно по­думать, что при создании объекта BigEgg должен использоваться «переопреде­ленный» класс Yolk, но это отнюдь не так, как видно из результата работы про­граммы.
Этот пример просто показывает, что при наследовании от внешнего класса ничего особенного с внутренними классами не происходит. Два внутренних класса — совершенно отдельные составляющие, с независимыми пространства­ми имен. Впрочем, возможность явного наследования от внутреннего класса со­хранилась:
//: innerclasses/BigEgg2.java
// Правильное наследование внутреннего класса,
i mport stati с net.mi ndvi ew.uti1.Pri nt.*;
class Egg2 {
protected class Yolk {
public YolkO { print("Egg2.YolkO"): } public void f() {