Студопедия.Орг Главная | Случайная страница | Контакты | Мы поможем в написании вашей работы!  
 

Интерфейсные классы



Концепция интерфейсов – одна из центральных в современных реализациях объектных языков, хотя к «классическим» концепциям объектной технологии она не относится. Понятие интерфейсных классов (ИК) впервые было введено в широкую практику в языке Java и быстро завоевало популярность. В Delphi Pascal этот механизм появился, начиная с версии 4.0. Очень активно интерфейсы используются в Java 2 и MS.NET. В стандартном С++ в чистом виде этого механизма нет, но он есть, например, в реализации языка для платформы.NET.

Интерфейсный класс – это полностью абстрактный класс, то есть все его методы являются абстрактными и представлены в классе только своими заголовками. Интерфейсные классы вводятся не для создания объектов-экземпляров, а для конструирования «рабочих» классов, определяя протокол взаимодействия клиентов с этими классами.

Интерфейсные классы были введены как альтернатива множественному наследованию, реализованному только в языке С++. Использование интерфейсных классов снимает большинство трудноразрешимых проблем, возникающих при практическом использовании множественного наследования. Если для обычных классов реализовано только простое наследование, то для интерфейсных классов – множественное, т.е. новый интерфейсный класс можно создавать на основе нескольких родительских интерфейсов. При этом новый ИК автоматически будет включать заголовки методов из всех родительских ИК, добавляя при необходимости новые заголовки. Тем самым интерфейсные классы создают свою многоуровневую иерархию, которая в общем случае имеет не древовидный, а сетевой характер (ориентированный граф).

Поскольку интерфейсные классы создаются для поддержки обычных классов, то возникает вопрос об их взаимодействии. Обычный (не интерфейсный) класс можно создавать на основе одного или нескольких интерфейсных классов. Говорят, что в этом случае класс применяет или реализует указанные интерфейсы. При этом новый класс обязан дать программную реализацию всех методов, перечисленных в виде заголовков в указанных родительских интерфейсных классах. Если хотя бы один метод не будет реализован, то новый класс должен быть объявлен абстрактным. Такая ситуация тоже вполне возможна и встречается довольно часто.

В итоге объектная модель разрабатываемой программной системы может содержать следующие разновидности классов:

· Интерфейсные классы, декларирующие (но не реализующие!) некоторое поведение, которое должны поддерживать рабочие классы; интерфейсные классы содержат только заголовки методов и общие неизменяемые свойства-константы и могут наследовать сразу от нескольких интерфейсов (и только интерфейсов!).

· Абстрактные классы, построенные на основе одного родительского класса и нескольких интерфейсных классов; абстрактные классы могут содержать изменяемые свойства и программный код некоторых методов, но основное назначение абстрактных классов – создание в иерархии классов некоторой общности свойств и методов, наследуемых дочерними «рабочими» классами.

· «Рабочие» классы, созданные на основе одного родителя и применяющие (реализующие) все заявленные интерфейсы; все методы должны иметь программную реализацию, и именно эти классы служат основой создания экземпляров-объектов.

Схематично взаимодействие этих классов можно показать следующим образом.


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

 
 


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

Этап 1. Создаются три интерфейсных класса:

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

· Интерфейс IStudent наследует протокол поведения, заявленный в классе Iperson, и дополняет его специфическим поведением студентов (например, обработкой учебных данных студента, оплатой обучения и др.).

· Интерфейс ISotr также наследует протокол поведения, заявленный в классе IPerson, но дополняет его поведением, характерным уже для сотрудников (например, начисление зарплаты, учет отработанного времени и т.д.).

Этап 2. Создаются следующие четыре класса:

· Абстрактный класс Person вводит необходимые персональные свойства и реализует интерфейс Iperson.

· Класс Student наследует класс Person, вводит необходимые свойства для хранения студенческих данных и реализует интерфейс Istudent.

· Класс Sotr наследует класс Person, вводит необходимые свойства для хранения данных сотрудников и реализует интерфейс Isotr.

· Класс StudSotr наследует класс Person, вводит необходимые свойства для хранения учебных и рабочих данных и реализует ДВА интерфейса – IStudent и Isotr.

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

Схематично взаимодействие указанных классов представлено на следующем рисунке.

 
 


Для описания всех этих классов на языке Java необходимо сделать следующие объявления (для наглядности приводятся только некоторые свойства и методы):

interface IPerson

{ void SetFam (string aFam);

string GetFam ();

}

interface IStudent extends IPerson

{ void SetOplata (); // заголовок метода расчета оплаты за обучение

int GetOplata ();

}

interface ISotr extends IPerson

{ void SetZarplata (); // заголовок метода расчета зарплаты

int GetZarplata ();

}

class Person implements IPerson

{ private string Fam;

public void SetFam (string aFam) { Fam = aFam;}

public string GetFam () {return Fam;}

}

class Student extends Person implements IStudent

{ private int Oplata; // свойство - сумма оплаты за обучение

public void SetOplata (); { код для обычных студентов}

public int GetOplata () {return Oplata;}

};

class Sotr extends Person implements ISotr

{ private int Zarplata; // свойство – зарплата сотрудника

public void SetZarplata () {код для обычных сотрудников}

public int GetZarplata () {return Zarplata;}

};

class StudSotr extends Person implements IStudent, ISotr

{ private int Oplata;

private int Zarplata;

public void SetOplata () { код для студентов-сотрудников}

public int GetOplata () {return Oplata;}

public void SetZarplata () {код для сотрудников-студентов}

public int GetZarplata () {return Zarplata;}

}

Комментарии к сделанным объявлениям:

1. Интерфейсные классы объявляются с помощью директивы interface.

2. При наследовании интерфейсов используется та же директива extends, что и для обычных классов.

3. При описании интерфейсов никакие директивы ограничения доступа можно не использовать, т.к. автоматически все методы интерфейсных классов считаются открытыми (public).

4. При описании класса для указания реализуемых в классе интерфейсов используется директива implements.

Как было отмечено выше, объекты интерфейсных классов не создаются, но можно объявлять и использовать переменные интерфейсных классов. Это особенно важно при объявлении формальных параметров методов. В частности, можно объявить метод с формальным параметром типа IPerson:

SomeMethod (IPerson aPar);

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

SomeMethod (aStud); SomeMethod (aSotr); SomeMethod (aStudSotr);

Этот механизм очень широко используется в стандартных библиотеках классов, в частности, при реализации коллекций-контейнеров, к рассмотрению которых теперь уже можно подойти вплотную. Рассмотрим организацию классов-коллекций в языке Java 2, отметив, что очень похоже коллекции реализованы в библиотеке.NET Framework.

Прежде всего, необходимо дать краткое описание набора интерфейсов, используемых в контейнерной библиотеке Java 2.

Базовый интерфейс Collection вводит наиболее общий набор методов обработки последовательностей элементов (объектных указателей типа Object) - добавление одного или целой группы элементов, удаление одного или всех элементов, проверка наличия элемента и пустоты контейнера, запрос текущего числа элементов в контейнере. Этот интерфейс не реализуется «рабочими» контейнерами, а служит лишь основой для создания более функциональных интерфейсов.

Потомками интерфейса Collection являются интерфейсы List и Set. Первый из них добавляет методы для индексирования элементов последовательности: добавление элемента по его индексу со сдвигом хвостовой части, запрос элемента по его индексу, запрос индекса первого или последнего появления заданного элемента, замена элемента в заданной позиции. Индексация элементов начинается с нуля. Именно на основе интерфейса List создаются очень популярные классы-контейнеры ArrayList и LinkedList.

Интерфейс Set определяет неупорядоченный набор неповторяющихся элементов. Новые методы в этом интерфейсе не добавляются, просто на метод добавления возлагается обязанность проверять наличие элемента в наборе. На основе интерфейса Set создается класс HashSet и интерфейс SortedSet. Последний предназначен для создания упорядоченных множеств, элементы в котором отсортированы либо естественным образом, либо с помощью специального интерфейса Comparartor. На основе интерфейса SortedSet создается класс TreeSet.

Фрагмент иерархии контейнеров для данной группы коллекций представлен на следующей схеме:


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

Класс ArrayList реализует список на базе массива, что определяет высокую скорость доступа к отдельным элементам, но затрудняет вставку и удаление элементов в начале и середине списка. Класс реализует три конструктора для создания пустого массива нулевой емкости, пустого массива заданной емкости и массива элементов на основе уже существующей коллекции. Для перемещения по массиву используется специальный объект-итератор.

Класс LinkedList реализует двунаправленный динамический список с возможностью быстрого добавления и удаления элементов в любом месте. Имеет два конструктора для создания пустого списка и списка на основе уже существующей коллекции. В дополнение к методам интерфейса List реализованы методы добавления, удаления и доступа к первому и последнему элементам, что позволяет использовать объекты этого класса для реализации стеков и очередей. Отдельных классов для этих разновидностей структур нет.

Класс HashSet реализует хеш-таблицу неповторяющихся элементов с очень высокой скоростью поиска, не зависящей от объема хранимых данных. Имеет четыре конструктора для создания хеш-таблиц разного размера. Аналогично другим классам этой группы для циклической обработки используются итераторы.

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

Вторую группу контейнеров составляют так называемые таблицы (map), или словари, или ассоциативные массивы как наборы пар «ключ-значение». Поиск значения производится по ключу. И ключ, и значение являются объектами самого общего типа Object. Основу этой группы составляют два интерфейса – базовый интерфейс Map и его потомок SortedMap.

Отдельного обсуждения заслуживают дополнительные интерфейсы, необходимые для реализации алгоритмов обработки коллекций. Как известно, две основные задачи обработки любых наборов данных – это поиск и сортировка. Для их реализации надо уметь организовывать циклический проход по набору и сравнение элементов набора. С этой целью в базовую библиотеку классов введены интерфейсы Iterator, ListIterator и Comparator.

В интерфейсе Iterator объявлены два основных метода:

· метод hasNext () возвращает true, если в коллекции еще есть элементы;

· метод next () выполняет переход к следующему элементу коллекции и возвращает полиморфный указатель на него.

Каждый контейнерный класс содержит специальный метод iterator (), который возвращает реализацию интерфейса Iterator для данного класса. Прежде всего, надо получить эту реализацию итератора, после чего можно обращаться к его методам для циклической обработки коллекции. Например, для контейнера ArrayList можно записать следующие операторы:

ArrayList MyArrList = new ArrayList(); // создание контейнера

MyArrList.Add («first item»); // добавление трех строк

MyArrList.Add («second item»);

MyArrList.Add («third item»);

Iterator MyIter = MyArrList.iterator(); // создание итератора

while (MyIter.hasNext()) System.out.println(MyIter.next()); // вывод

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

Интерфейс ListIterator расширяет интерфейс Iterator и является более функциональным, поскольку позволяет не только проходить коллекцию в прямом или обратном направлении, но и выполнять операции добавления, удаления или замены элементов. В этот интерфейс добавлены следующие методы:

· hasPrevious () проверяет наличие предыдущего элемента;

· previous () выполняет переход к предыдущему элементу и возвращает объектный указатель на него;

· add (Object item) добавляет элемент-объект перед текущим элементом

· nextindex () возвращает индекс текущего элемента;

· previousindex () возвращает индекс предыдущего элемента;

· set (Object item) заменяет текущий элемент на элемент item.

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

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

1. Объявление класса, применяющего интерфейс Comparator и содержащего необходимую реализацию метода compare:

class NewCompare implements Comparator

{ public int compare (Object aObj1, Object aObj2)

{ реализация сравнения входных объектов }

};

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

TreeSet MyTree = new TreeSet(new NewCompare());

3. Заполнение контейнера в том порядке, который определен заданным компаратором.

Еще одним интересным моментом стандартной библиотеки контейнеров языка Java является наличие в ней класса Collections, содержащего программную реализацию основных алгоритмов обработки данных. Эти алгоритмы реализованы как методы класса (директива static), что позволяет обращаться к ним без создания объектов данного класса, а используя только имя класса и имя метода. Алгоритмы реализуют следующие основные операции: поиск минимального или максимального элемента в коллекции, копирование элементов одной коллекции в другую, двоичный поиск заданного элемента, случайное перемешивание коллекции, сортировка элементов (в том числе с помощью заданного компаратора). Некоторые авторы отмечают, что на создание библиотеки контейнеров языка Java большое идеологическое влияние оказала концепция обобщенных функций, реализованная наиболее полно в языке С++ в виде так называемых шаблонов. Данная концепция кратко рассматривается в следующем разделе пособия.

А в завершение данного раздела отметим, что объявление и использование интерфейсных классов в Delphi Pascal выглядит следующим образом:

type ISotr = interface (IHuman) // объявление интерфейса

заголовки методов;

end;

TStudSotr = class (THuman, IStud, ISotr) // реальный класс

объявление свойств;

заголовки методов интерфейса IStud;

заголовки методов интерфейса ISotr;

заголовки собственных методов;

end;

Еще раз напомним, что после описания тела класса обязательно приводится программная реализация всех заявленных методов.





Дата публикования: 2014-11-02; Прочитано: 3729 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!



studopedia.org - Студопедия.Орг - 2014-2024 год. Студопедия не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования (0.016 с)...