Синтаксис и семантика использования внутренних классов и интерфейсов

Причины использования интерфейсов, их реализация. Создание внутреннего класса внутри метода или в случайном контексте и приведение его к базовому типу, доступ к элементам окружающего его класса, ссылка на внешний объект, примеры его использования.

Рубрика Программирование, компьютеры и кибернетика
Вид книга
Язык русский
Дата добавления 20.11.2009
Размер файла 65,8 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Пример. Если бы Sequence.java не использовал бы внутренние классы, то Вы бы сказали Sequence это есть Selector, и Вы бы могли иметь только один Selector в Sequence. Так же, у вас не было бы второго метода getRSelector( ), который происходит от Selector, который собственно двигается в обратном направлении по последовательности. Гибкость такого рода доступна только с использованием внутренних классов.

Замыкания & обратные вызовы

Замыкание это объект, который хранит информацию из контекста в котором он был создан. Из этого описания, Вы можете видеть, что внутренний класс замкнут на объектах, поскольку он не содержит каждый из кусочков из внешнего класса (контекста, в котором он был создан), но он автоматически содержит ссылку на этот внешний класс, где у него есть доступ к элементам класса, даже к private.

Наиболее неоспоримым аргументом для включения можно назвать разновидность указательного механизма в Java позволяющего осуществлять обратные вызовы (callbacks). С обратным вызовом, некоторые другие объекты, могут получить кусочек информации, которая позволит им в дальнейшем передать управление в исходящий объект. Это очень мощная концепция, как Вы увидите потом, в Главе 13 и Главе 16. Если обратный вызов реализуется через использование указателя, то Вы должны очень осторожно с ним обращаться. Как Вы наверное уже могли понять, в Java имеется тенденция для более осторожного программирования, поэтому указатели не включены в этот язык.

"Замыкание" предоставляемое внутренними классами - лучшее решение, чем указатели. Оно более гибкое и намного более безопасное. Вот пример:

//: c08:Callbacks.java

// Использование внутренних классов для возврата

interface Incrementable {

void increment();

}

// Очень просто реализовать интерфейс:

class Callee1 implements Incrementable {

private int i = 0;

public void increment() {

i++;

System.out.println(i);

}

}

class MyIncrement {

public void increment() {

System.out.println("Other operation");

}

public static void f(MyIncrement mi) {

mi.increment();

}

}

// Если ваш класс должен реализовать increment() по другому,

// Вы должны использовать внутренний класс:

class Callee2 extends MyIncrement {

private int i = 0;

private void incr() {

i++;

System.out.println(i);

}

private class Closure implements Incrementable {

public void increment() { incr(); }

}

Incrementable getCallbackReference() {

return new Closure();

}

}

class Caller {

private Incrementable callbackReference;

Caller(Incrementable cbh) {

callbackReference = cbh;

}

void go() {

callbackReference.increment();

}

}

public class Callbacks {

public static void main(String[] args) {

Callee1 c1 = new Callee1();

Callee2 c2 = new Callee2();

MyIncrement.f(c2);

Caller caller1 = new Caller(c1);

Caller caller2 =

new Caller(c2.getCallbackReference());

caller1.go();

caller1.go();

caller2.go();

caller2.go();

}

} ///:~

Этот пример так же показывает дальнейшие различия между реализацией интерфейса во внешнем классе и того же самого во внутреннем. Callee1 простое решение в терминах кода. Callee2 наследует от MyIncrement, который уже имеет отличный метод increment( ), который в свою очередь что то делает, при этом еу нужен интерфейс Incrementable. Когда MyIncrement наследуется в Callee2, increment( ) уже не может быть переопределен для использования с Incrementable, поэтому принудительно использовано разделение реализаций с использованием внутреннего класса. Так же заметьте, когда Вы создаете внутренний класс вам уже не нужно добавлять или модифицировать интерфейс внешнего класса.

Обратите внимание на то, что все исключая getCallbackReference( ) в Callee2 с модификатором private. Для того, что бы разрешить любые соединения с внешним миром, можно использовать интерфейс Incrementable. Далее Вы увидите, как интерфейсы поддерживают полное разделение интерфейса и реализации.

Внутренний класс Closure просто реализует Incrementable для того, что бы безопасно перехватить возврат Callee2. Единственный способ получить эту ссылку это вызов increment( ).

Caller передает Incrementable ссылку в его конструктор (хотя захват обратной ссылки может происходить в любое время) и затем, немного погодя, использует эту ссылку для возврата в класс Callee.

Значение обратного вызова очень гибкое, Вы можете на ходу решить, в какую функцию будет передано управление во время исполнения программы. Преимущества данной техники будут изложены несколько позднее в главе 13, где обратные вызовы будут использоваться очень часто для реализации графического интерфейса пользователя.

Внутренние классы и структуры управления

Более конкретный пример использования внутренних классов может быть получен в нечто называемом здесь как структуры управления.

Структура управления приложением это класс или набор классов, которые спроектированы для решения частных проблем. Для того, что бы применить структуру управления приложения, Вы должны наследовать от одного или нескольких этих классов и переопределить некоторые методы. Код, который Вы напишите в переопределенных методах, подстроит решения проблем под ваши конкретные задачи. Структура управления - частный тип структур управления приложениями с доминирующей функцией ответа на события; система, которая в основном занимается обработкой событий называется системой обработки событий. Одной из наиболее важных проблем в программировании приложений можно назвать графический пользовательский интерфейс (GUI), который почти полностью завязан на обработке событий. Как Вы сможете увидеть в главе 13, библиотека Java Swing - структура управления, которая элегантно решает проблемы GUI и очень много использует внутренние классы.

Для того, что бы увидеть, как внутренние классы позволяют с легкостью создавать и использовать структуры управления, давайте представим себе структуру управления, чьей задачей будет выполнение событий, когда они будут готовы. Хотя "готовы" здесь может означать что угодно, поэтому выберем вариант зависящий от времени. Теперь у нас есть структура управления без какой либо конкретной информации о том, что же ей нужно в действительности контролировать. Сперва, тут есть интерфейс, который описывает управляемые события. В этой роли выступает абстрактный класс, вместо настоящего интерфейса, поскольку у нас используется поведение на основе таймера:

//: c08:controller:Event.java

// Общие методы для любого контроля осбытий.

package c08.controller;

abstract public class Event {

private long evtTime;

public Event(long eventTime) {

evtTime = eventTime;

}

public boolean ready() {

return System.currentTimeMillis() >= evtTime;

}

abstract public void action();

abstract public String description();

} ///:~

Конструктор просто запоминает время, когда Вы хотите запустить Event, затем ready( ) сообщает, когда приходит время для запуска. Естественно ready( ) должен быть переопределен в дочернем классе на нечто более другое, чем время.

Action( ) - метод, который вызывается, когда Event уже (готов) ready( ), а description( ) дает текстовое сопровождение об этом Event.

Следующий файл содержит структуру управления, которая управляет и удаляет события. Первый класс - простой помощник, чья работа заключается только в содержании объектов Event. Вы можете заменить его любым подходящим контейнером, а в главе 9 Вы откроете для себя другие контейнеры, которые делают этот же трюк, но без какого либо дополнительного кода:

//: c08:controller:Controller.java

// Вместе с Event, изначальная

// система управления для всех систем управления:

package c08.controller;

// Это просто способ содержаня объектов Event.

class EventSet {

private Event[] events = new Event[100];

private int index = 0;

private int next = 0;

public void add(Event e) {

if(index >= events.length)

return; // (В настоящей жизни нужно обрабатывать исключение)

events[index++] = e;

}

public Event getNext() {

boolean looped = false;

int start = next;

do {

next = (next + 1) % events.length;

// Смотрим, не зациклился ли он:

if(start == next) looped = true;

// Если зациклился, то обнуляем список

//

if((next == (start + 1) % events.length)

&& looped)

return null;

} while(events[next] == null);

return events[next];

}

public void removeCurrent() {

events[next] = null;

}

}

public class Controller {

private EventSet es = new EventSet();

public void addEvent(Event c) { es.add(c); }

public void run() {

Event e;

while((e = es.getNext()) != null) {

if(e.ready()) {

e.action();

System.out.println(e.description());

es.removeCurrent();

}

}

}

} ///:~

EventSet поддерживает Event-ов. (Если бы использовался настоящий контейнер из главы 9, то вам не нужно было бы беспокоиться о максимальном размере, поскольку он изменяет размер самостоятельно). Index используется для сохранения пути на следующее свободное место, а next используется, когда Вы ищите следующий Event в списке, что бы понять, не зациклились ли Вы. Информация о зацикливании особенна важна при вызове getNext( ), поскольку объекты Event должны удаляться из списка (используется removeCurrent( )) как только они были выполнены, поэтому getNext( ) должен найти промежутки в списке и осуществлять выборку без них.

Заметьте, что removeCurrent( ) не просто устанавливает некоторый флаг, сигнализирующий, что объект уже не используется. Вместо этого он устанавливает ссылку в null. Это поведение достаточно важно, поскольку сборщик мусора не сможет очистить объект, на который все еще существует ссылка. Если Вы думаете, что ваши ссылки могут таким образом зависнуть, то тогда хорошей идеей устанавливать их в null, дабы дать возможность сборщику мусора удалить их.

Controller - то место, где на самом деле вся работа и происходит. Он использует EventSet для поддержки его объектов Event, а метод addEvent( ) позволяет вам добавлять новые события в список. Но все таки главным методом является run( ). Этот метод циклически просматривает EventSet, выискивая объекты Event которые ready( ) (готовы) для обработки. Для всех объектов, которые готовы, он вызывает метод action( ), печатает описание - description( ), а затем удаляет событие - Event из списка.

Заметьте, что пока Вы ничего не знаете о том, что же на самом делают эти Event. И вот это и есть основная проблема проектировки; как отделить те вещи, которые должны изменяться от тех вещей, которые всегда постоянны? Или, если воспользоваться моими терминами - "вектор изменения", различные поведения различных типов объектов Event. Здесь Вы можете выражать различные действия созданием различных подклассов Event.

Как раз здесь в игру и вступают внутренние классы. Они предоставят в ваше распоряжение две необходимые возможности:

1. Для создания полной реализации управляющей структуры приложения в едином классе, нужно инкапсулировать все уникальные части, которые реализуются. Внутренние классы используются для отображения видов action( ) необходимых для решения проблемы. В дополнение, следующий пример использует private внутренний класс, так что его реализация полностью скрыта и не может быть безнаказанно изменена.

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

Рассмотрим частную реализацию структуры управления, спроектированную для управления функциями гринхауза (greenhouse functions)[43]. Каждое из действий полностью уникально и отличается от других: включение света и термостатов, выключение их, звон колокольчиков и рестартинг всей системы. Но структура управления просто изолирована в этом отличном (от других) коде. Внутренний класс позволяет вам получить множественно наследуемые классы от одного и того же базового класса (т.е. несколько наследников от одного в одном), Event, в одном единственном классе. Для каждого типа действия Вы наследуете новый внутренний класс от Event и пишите код контроля внутри action( ).

Как и для всех остальных структур управления, класс GreenhouseControls наследуется от Controller:

//: c08:GreenhouseControls.java

import c08.controller.*;

public class GreenhouseControls

extends Controller {

private boolean light = false;

private boolean water = false;

private String thermostat = "Day";

private class LightOn extends Event {

public LightOn(long eventTime) {

super(eventTime);

}

public void action() {

// Сюда нужно поместить код управлением светом

light = true;

}

public String description() {

return "Light is on";

}

}

private class LightOff extends Event {

public LightOff(long eventTime) {

super(eventTime);

}

public void action() {

// Сюда для выключения света

light = false;

}

public String description() {

return "Light is off";

}

}

private class WaterOn extends Event {

public WaterOn(long eventTime) {

super(eventTime);

}

public void action() {

// сюда код управления

water = true;

}

public String description() {

return "Greenhouse water is on";

}

}

private class WaterOff extends Event {

public WaterOff(long eventTime) {

super(eventTime);

}

public void action() {

// сюда код управления

water = false;

}

public String description() {

return "Greenhouse water is off";

}

}

private class ThermostatNight extends Event {

public ThermostatNight(long eventTime) {

super(eventTime);

}

public void action() {

// Сюда код управления

thermostat = "Night";

}

public String description() {

return "Thermostat on night setting";

}

}

private class ThermostatDay extends Event {

public ThermostatDay(long eventTime) {

super(eventTime);

}

public void action() {

// сюда код управления

thermostat = "Day";

}

public String description() {

return "Thermostat on day setting";

}

}

private int rings;

private class Bell extends Event {

public Bell(long eventTime) {

super(eventTime);

}

public void action() {

// Звонить каждые 2 секунды, 'rings' раз:

System.out.println("Bing!");

if(--rings > 0)

addEvent(new Bell(

System.currentTimeMillis() + 2000));

}

public String description() {

return "Ring bell";

}

}

private class Restart extends Event {

public Restart(long eventTime) {

super(eventTime);

}

public void action() {

long tm = System.currentTimeMillis();

// Конфигурация из текстового файла

rings = 5;

addEvent(new ThermostatNight(tm));

addEvent(new LightOn(tm + 1000));

addEvent(new LightOff(tm + 2000));

addEvent(new WaterOn(tm + 3000));

addEvent(new WaterOff(tm + 8000));

addEvent(new Bell(tm + 9000));

addEvent(new ThermostatDay(tm + 10000));

// Может быть добавлен объект рестарта

addEvent(new Restart(tm + 20000));

}

public String description() {

return "Restarting system";

}

}

public static void main(String[] args) {

GreenhouseControls gc =

new GreenhouseControls();

long tm = System.currentTimeMillis();

gc.addEvent(gc.new Restart(tm));

gc.run();

}

} ///:~

Заметьте, что light, water, thermostat и rings связаны с внешним классом GreenhouseControls и поэтому внутренние классы могут получать доступ к его полям без каких либо особенностей или специального доступа. Так же многие из методов action( ) осуществляют некоторый вид аппаратного контроля, который лучше всего перевести не в Java код.

Большинство из классов Event выглядят одинаково, но Bell и Restart особенны. Bell звонит и если он не звонит некоторое время, то добавляется новый объект Bell в список, так что он прозвонит позже. Заметьте, что внутренние классы почти что выглядят, как множественное наследование: Bell содержит все методы Event и он так же имеет доступ ко всем метода внешнего класса GreenhouseControls.

Restart ответственен за инициализацию системы, он добавляет все необходимые события. Естественно, лучше было бы отказаться от жестко зашитых событий в программе и читать их из файла. (Упражнение в главе 11 попросит вас сделать данное предположение.) Поскольку Restart( ) это просто другой объект Event, Вы так же можете добавить объект Restart внутрь Restart.action( ) так что система будет периодически рестартовать сама себя. И все, что нужно будет сделать это лишь в main( ) создать объект GreenhouseControls и добавить объект Restart который выполнял бы эту работу.

Резюме

Интерфейсы и внутренние классы более мудреная концепция, чем те, которые Вы можете найти в других ОО языках программирования. К примеру, ничего похожего в C++ просто нет. Сообща они решают некоторые проблемы, которые в C++ решаются путем множественного наследования. Но все равно в C++ использовать множественное наследование достаточно сложно, если сравнивать с Java, где интерфейсы и вложенные классы использовать гораздо проще.

Хотя при всей мощи предоставляемой этим средствами языка, Вы должны на стадии проектировки решать, что же использовать, интерфейс или внутренний класс, или оба. Хотя в этой книге, в этой главе обсуждается только синтаксис и семантика использования внутренних классов и интерфейсов, вам все-таки предстоит на собственном опыте выявить те случаи, когда разумно было бы применить именно их.


Подобные документы

  • Разработка шаблонной функции FindMax, выполняющей поиск максимального элемента в массиве. Разработка шаблонного класса CMyArray, представляющего собой массив элементов некоторого типа. Иерархия классов и интерфейсов. Индексированный доступ к элементам.

    лабораторная работа [419,0 K], добавлен 03.11.2014

  • Изображение класса на диаграмме UML. Инкапсуляция как средство защиты его внутренних объектов. Использование принципа полиморфизма для реализации механизма интерфейсов. Создание новых классов путем наследования. Ассоциация как вид отношений между ними.

    лекция [516,6 K], добавлен 03.12.2013

  • Механизм классов в C++. Инициализация внутреннего объекта с помощью конструктора. Управление доступом к классу. Защищенные члены класса. Графические средства компилятора Borland C 3.1. Библиотека стандартных шаблонов. Реализация и использование класса.

    курсовая работа [2,7 M], добавлен 16.05.2012

  • Оценка функциональных возможностей стандартных классов представления данных на примерах использования избранных методов ("detect: ifNone:" класса Set, "to:by:do:" класса Number и "copy: ReplaceFrom: to: with:" класса OrderedCollection), их тестирование.

    лабораторная работа [1,1 M], добавлен 14.10.2012

  • Возможности использования Internet-ресурсов в средней школе. Мониторинг качества образовательных сайтов в России. Создание образовательного сайта по информатике для 10-го класса. Анализ практического использования образовательного сайта "Информатика".

    дипломная работа [3,2 M], добавлен 10.03.2012

  • Объединение данных с функциями их обработки в сочетании со скрытием ненужной для использования этих данных информации. Описание классов, их свойства. Спецификаторы доступа private и public. Доступ к элементам объекта. Сущность константного метода.

    лабораторная работа [485,9 K], добавлен 22.10.2013

  • Характеристика интерфейса в Java, возможность его расширения с использованием механизма наследования. Организация обратного вызова в Java. Сущность внутреннего класса. Обращение из внутреннего класса к элементам внешнего класса и листинг программы.

    методичка [90,8 K], добавлен 30.06.2009

  • Обзор технологии OpenStack, область ее применения. Реализация библиотеки классов. Реализация базовых классов и интерфейсов архитектуры. Создание виртуального сервера. Интеграция разработанной библиотеки классов и архитектура проектного решения.

    дипломная работа [1,0 M], добавлен 09.08.2016

  • Web-сервис как программная система, идентифицируемая с помощью некоторого URI, общедоступный интерфейс и связывания которого определяются и описываются с помощью языка описания интерфейсов WSDL. История, коммерческие предпосылки использования сервисов.

    контрольная работа [169,1 K], добавлен 19.01.2012

  • Анализ графических пользовательских интерфейсов современных систем оптимизации программ. Создание математической модели и алгоритма системы управления СБкЗ_ПП, ее архитектурно-контекстная диаграмма. Техническая документация программного средства.

    дипломная работа [1,1 M], добавлен 18.04.2012

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.