Интерфейс (объектно-ориентированное программирование)
Интерфе́йс (англ. interface) — структура программы или синтаксиса, определяющая отношение с объектами, объединёнными некоторым поведением. При проектировании классов разработка интерфейса тождественна разработке спецификации (множества методов, которые каждый класс, использующий интерфейс, должен реализовывать). Интерфейсы, наряду с абстрактными классами и протоколами, устанавливают взаимные обязательства между элементами программной системы, что является фундаментом концепции программирования по контракту (англ. design by contract, DbC). Интерфейс определяет границу взаимодействия между классами или компонентами, специфицируя определённую абстракцию, которую осуществляет реализующая сторона. Интерфейс в ООП является строго формализованным элементом языка и широко используется в исходном коде программ. Интерфейсы позволяют наладить множественное наследование объектов и в то же время решить проблему ромбовидного наследования. В языке C++ она решается через наследование классов с использованием ключевого слова Описание и использование интерфейсовОписание интерфейса, если отвлечься от деталей синтаксиса конкретных языков, состоит из двух частей: имени и методов интерфейса.
Использование интерфейсов возможно двумя способами:
Как правило, в объектно-ориентированных языках программирования интерфейсы, как и классы, могут наследоваться друг от друга. В этом случае интерфейс-потомок включает все методы интерфейса-предка и, возможно, добавляет к ним свои собственные. Таким образом, с одной стороны, интерфейс — это «договор», который обязуется выполнить класс, реализующий его, а с другой стороны, интерфейс — это тип данных, потому что его описание достаточно чётко определяет свойства объектов, чтобы наравне с классом типизировать переменные. Следует однако подчеркнуть, что интерфейс не является полноценным типом данных, так как он задаёт только внешнее поведение объектов. Внутреннюю структуру и реализацию заданного интерфейсом поведения обеспечивает класс, реализующий интерфейс; именно поэтому «экземпляров интерфейса» в чистом виде не бывает и любая переменная типа «интерфейс» содержит экземпляры конкретных классов. Использование интерфейсов — один из вариантов обеспечения полиморфизма в объектных языках и средах. Все классы, реализующие один и тот же интерфейс с точки зрения определяемого ими поведения ведут себя внешне одинаково. Это позволяет писать обобщённые алгоритмы обработки данных, использующие в качестве типов параметры интерфейсов, и применять их к объектам различных типов. Например, интерфейс Что может и что не может содержать интерфейс«Чистый» интерфейс содержит только названия функций, которые будут определены где-то в классе-потомке, без тел. Однако для удобства программиста языки программирования и требования к коду могут допускать в интерфейсах определённые виды данных и функций.
В любом случае интерфейс не может содержать:
Интерфейсы и абстрактные классыМожно заметить, что интерфейс, с формальной точки зрения, — это просто чистый абстрактный класс, то есть класс, в котором не определено ничего, кроме абстрактных методов. Если язык программирования поддерживает множественное наследование и абстрактные методы (как например C++), то необходимости во введении в синтаксис языка отдельного понятия «интерфейс» не возникает. Данные сущности описываются с помощью абстрактных классов и наследуются классами для реализации абстрактных методов. Однако поддержка множественного наследования в полном объёме достаточно сложна и вызывает множество проблем как на уровне реализации языка, так и на уровне архитектуры приложений. Введение понятия интерфейсов является компромиссом, позволяющим получить многие преимущества множественного наследования (в частности, возможность удобно определять логически связанные наборы методов в виде сущностей, подобных классам и допускающим наследование и реализацию), не реализуя его в полном объёме и не сталкиваясь с большинством связанных с ним трудностей. На уровне исполнения классическая схема множественного наследования вызывает дополнительный ряд неудобств:
Использование схемы с интерфейсами (вместо множественного наследования) позволяет решить эти проблемы, если не считать вопроса о вызове интерфейсных методов (то есть виртуальных вызовов методов при множественном наследовании, см. выше). Классическое решение состоит в том (например, в JVM для Java или CLR для C#), что интерфейсные методы вызываются менее эффективным способом, без помощи виртуальной таблицы: при каждом вызове сначала определяется конкретный класс объекта, а затем в нём ищется нужный метод (разумеется, с многочисленными оптимизациями). Множественное наследование и реализация интерфейсовКак правило, языки программирования разрешают наследовать интерфейс от нескольких интерфейсов-предков. Все методы, объявленные в интерфейсах-предках, становятся частью объявления интерфейса-потомка. В отличие от наследования классов, множественное наследование интерфейсов гораздо проще реализуется и не вызывает существенных затруднений. Тем не менее, одна коллизия при множественном наследовании интерфейсов и при реализации нескольких интерфейсов одним классом всё-таки возможна. Она возникает, когда в двух или более интерфейсах, наследуемых новым интерфейсом или реализуемых классом, имеются методы с одинаковыми сигнатурами. Разработчики языков программирования вынуждены выбирать для таких случаев те или иные способы разрешения противоречий. Вариантов здесь несколько:
Интерфейсы в конкретных языках и системахРеализация интерфейсов во многом определяется исходными возможностями языка и целью, с которой интерфейсы введены в него. Очень показательны особенности использования интерфейсов в языках Java, Object Pascal системы Delphi и C++, поскольку они демонстрируют три принципиально разные ситуации: изначальная ориентация языка на использование концепции интерфейсов, их применение для совместимости, а также их эмуляция классами.
DelphiВ Delphi интерфейсы были введены для поддержки технологии COM фирмы Microsoft. Однако при выпуске Kylix интерфейсы как элемент языка были «отвязаны» от технологии COM. Все интерфейсы наследуются от интерфейса Пример объявления интерфейса: IMyInterface = interface
procedure DoSomething;
end;
Для того, чтобы объявить о реализации интерфейсов, в описании класса необходимо указать их имена в скобках после ключевого слова Вышеупомянутая ориентированность интерфейсов Delphi на технологию COM привела к некоторым неудобствам. Дело в том, что интерфейс Пример класса, реализующего интерфейс: TMyClass = class(TMyParentClass, IMyInterface)
procedure DoSomething;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
implementation
Программист должен правильно реализовать методы Пример класса-наследника TMyClass = class(TInterfacedObject, IMyInterface)
procedure DoSomething;
end;
При наследовании класса, реализующего интерфейс, от класса без интерфейсов, программист должен реализовать упомянутые методы вручную, определив наличие либо отсутствие контроля по подсчёту ссылок, а также получение интерфейса в Пример произвольного класса без подсчёта ссылок: TMyClass = class(TObject, IInterface, IMyInterface)
//IInterface
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
//IMyInterface
procedure DoSomething;
end;
{ TMyClass }
function TMyClass.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function TMyClass._AddRef: Integer;
begin
Result := -1;
end;
function TMyClass._Release: Integer;
begin
Result := -1;
end;
procedure TMyClass.DoSomething;
begin
//Do something
end;
C++C++ поддерживает множественное наследование и абстрактные классы, поэтому, как уже упоминалось выше, отдельная синтаксическая конструкция для интерфейсов в этом языке не нужна. Интерфейсы определяются при помощи абстрактных классов, а реализация интерфейса производится путём наследования этих классов. Пример определения интерфейса: /**
* interface.Openable.h
*
*/
#ifndef INTERFACE_OPENABLE_HPP
#define INTERFACE_OPENABLE_HPP
// Класс интерфейса iOpenable. Определяет возможность открытия/закрытия чего-либо.
class iOpenable
{
public:
virtual ~iOpenable(){}
virtual void open()=0;
virtual void close()=0;
};
#endif
Интерфейс реализуется через наследование (благодаря наличию множественного наследования возможно реализовать в одном классе несколько интерфейсов, если в этом есть необходимость; в примере ниже наследование не множественное): /**
* class.Door.h
*
*/
#include "interface.Openable.h"
#include <iostream>
class Door: public iOpenable
{
public:
Door(){std::cout << "Door object created" << std::endl;}
virtual ~Door(){}
//Конкретизация методов интерфейса iOpenable для класса Door
void open() override {std::cout << "Door opened" << std::endl;}
void close() override {std::cout << "Door closed" << std::endl;}
//Специфические для класса Door свойства и методы
std::string mMaterial;
std::string mColor;
//...
};
/**
* class.Book.h
*
*/
#include "interface.Openable.h"
#include <iostream>
class Book: public iOpenable
{
public:
Book(){std::cout << "Book object created" << std::endl;}
virtual ~Book(){}
//Конкретизация методов интерфейса iOpenable для класса Book
void open() override {std::cout << "Book opened" << std::endl;}
void close() override {std::cout << "Book closed" << std::endl;}
//Специфические для класса Book свойства и методы
std::string mTitle;
std::string mAuthor;
//...
};
Тестируем всё вместе: /**
* test.Openable.cpp
*
*/
#include "interface.Openable.h"
#include "class.Door.h"
#include "class.Book.h"
//Функция открытия/закрытия любых разнородных объектов, в которых реализован интерфейс iOpenable
void openAndCloseSomething(iOpenable& smth)
{
smth.open();
smth.close();
}
int main()
{
Door myDoor;
Book myBook;
openAndCloseSomething(myDoor);
openAndCloseSomething(myBook);
system ("pause");
return 0;
}
JavaВ отличие от C++, Java не позволяет наследовать больше одного класса. В качестве альтернативы множественному наследованию существуют интерфейсы. Каждый класс в Java может реализовать любой набор интерфейсов. Порождать объекты от интерфейсов в Java нельзя. Объявление интерфейсовОбъявление интерфейсов очень похоже на упрощённое объявление классов. Оно начинается с заголовка. Сначала указываются модификаторы. Интерфейс может быть объявлен как Далее записывается ключевое слово После этого может следовать ключевое слово Наследование интерфейсов действительно очень гибкое. Так, если есть два интерфейса Затем в фигурных скобках записывается тело интерфейса. Пример объявления интерфейса: public interface Drawable extends Colorable, Resizable {
}
Тело интерфейса состоит из объявления элементов, то есть полей, констант и абстрактных методов. Все поля интерфейса автоматически являются public interface Directions {
int RIGHT=1;
int LEFT=2;
int UP=3;
int DOWN=4;
}
Все методы интерфейса являются public interface Moveable {
void moveRight();
void moveLeft();
void moveUp();
void moveDown();
}
Как видно, описание интерфейса гораздо проще, чем объявление класса. Реализация интерфейсаДля реализации интерфейса он должен быть указан при декларации класса с помощью ключевого слова interface I
{
void interfaceMethod();
}
public class ImplementingInterface implements I
{
void interfaceMethod()
{
System.out.println("Этот метод реализован из интерфейса I");
}
}
public static void main(String[] args)
{
ImplementingInterface temp = new ImplementingInterface();
temp.interfaceMethod();
}
Каждый класс может реализовать любые доступные интерфейсы. При этом в классе должны быть реализованы все абстрактные методы, появившиеся при наследовании от интерфейсов или родительского класса, чтобы новый класс мог быть объявлен неабстрактным. Если из разных источников наследуются методы с одинаковой сигнатурой, то достаточно один раз описать реализацию, и она будет применяться для всех этих методов. Однако, если у них различное возвращаемое значение, то возникает конфликт. Пример: interface A {
int getValue();
}
interface B {
double getValue();
}
interface C {
int getValue();
}
public class Correct implements A, C // класс правильно наследует методы с одинаковой сигнатурой
{
int getValue()
{
return 5;
}
}
class Wrong implements A, B // класс вызывает ошибку при компиляции
{
int getValue()
{
return 5;
}
double getValue()
{
return 5.5;
}
}
C#В C# интерфейсы могут наследовать один или несколько других интерфейсов. Членами интерфейсов могут быть методы, свойства, события и индексаторы: interface I1
{
void Method1();
}
interface I2
{
void Method2();
}
interface I : I1, I2
{
void Method();
int Count { get; }
event EventHandler SomeEvent;
string this[int index] { get; set; }
}
При реализации интерфейса класс должен реализовать как методы самого интерфейса, так и его базовых интерфейсов: public class C : I
{
public void Method()
{
}
public int Count
{
get { throw new NotImplementedException(); }
}
public event EventHandler SomeEvent;
public string this[int index]
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public void Method1()
{
}
public void Method2()
{
}
}
Интерфейсы в UML![]() Интерфейсы в UML используются для визуализации, спецификации, конструирования и документирования стыковочных UML-узлов между составными частями системы. Типы и UML-роли обеспечивают механизм моделирования статического и динамического соответствия абстракции интерфейсу в конкретном контексте. В UML интерфейсы изображаются как классы со стереотипом «interface», либо в виде кружочков (в этом случае содержащиеся в интерфейсе UML-операции не отображаются). См. также
ПримечанияСсылки
|
Portal di Ensiklopedia Dunia