logo
Языки программирования

15.4. Язык программирования Eiffel

Основные характеристики языка программирования Eiffel:

• Язык Eiffel изначально создавался как объектно-ориентированный, а не как дополнительная пристройка для поддержки ООП в существующем языке.

• В языке Eiffel программу можно построить единственным способом — как систему классов, которые являются клиентами друга друга или на­следуются один из другого.

• Поскольку наследование — это основная конструкция структурирова­ния, центральное место в языке занимает стандартная библиотека клас­сов (связанных наследованием).

• Не будучи частью «языка», развитая среда программирования была со­здана группой разработчиков языка Eiffel. Среда включает ориентиро­ванную на язык поддержку для отображения и изменения классов, для инкрементной компиляции и для тестирования и отладки.

В отличие от языка Smalltalk (который имеет аналогичные характеристики), язык Eiffel жестко придерживается статического контроля соответствия типов наряду с динамическим полиморфизмом, как в языках Ada 95 и C++. Eiffel идет дальше в попытках поддерживать надежное программирование, интегрируя утверждения в язык, как обсуждалось

в разделе 11.5.

Единственная программная единица в Eiffel — это класс: никаких файлов, как в языках С и C++, и никаких пакетов, как в языке Ada.

Терминология языка Eiffel отличается от других языков: подпрограммы (процедуры и функции) называются рутинами (routine), объекты (переменные и константы) называются атрибутами (attribute), а рутины и атрибуты, которые входят в состав класса, называются свойствами (feature) класса. По существу, нет никакого различия между функциями и константами: подобно литералу пере­числения языка Ada, константа рассматривается просто как функция без пара­метров. Язык Eiffel статически типизирован, подобно языку C++, в том смысле, что при присваиваниях и при передаче параметров типы должны соот­ветствовать друг другу, и это соответствие может быть проверено во время ком­пиляции. Однако язык не имеет таких богатых конструкций для управления со­ответствием типов, как подтипы и числовые типы (numerics) языка Ada.

Когда объявляется класс, задается список свойств:

class Airplanes

feature -- "public"

New_Airplane(Airplane_Data): Integer is

Do

….

end; -- New_Airplane Get_Airplane(lnteger): Airplane_Data is

do

….

end; -- Get_Airplane

feature {} --"private"

database: ARRAY[Airplane_Data];

current_airpianes: Integer;

find_empty_entry: Integer is

do

end; -- find_empty_entry

end; -- class Airplanes

Как и в языке C++, набор свойств может быть сгруппирован, и для каждой такой feature-группы может быть определена своя доступность, feature-груп­па со спецификатором, который изображает пустое множество «{}», не экс­портируется ни в какой другой класс, подобно private-спецификатору, fea­ture-группа без спецификатора экспортируется в любой другой класс в систе­ме; однако это отличается от public-спецификатора в языке C++ и от откры­той части спецификации пакета в языке Ada, потому что экспортируется толь­ко доступ для чтения. Кроме того, вы можете явно написать список классов в feature-спецификаторе; этим классам будет разрешен доступ к свойствам внутри группы, подобно «друзьям» в языке C++.

В языке Eiffel нет реального различия между предопределенными типами и типами, определенными программистом, database — это объект класса ARRAY, который является предопределенным в библиотеке языка Eiffel. Ко­нечно, «массив» — очень общее понятие; как мы должны указать тип элемен­тов массива? Нужно применить тот же самый метод, который использовал бы программист для параметризации любого типа данных: обобщения (genetics). Встроенный класс ARRAY имеет один родовой параметр, который использует­ся, чтобы определить тип элементов:

class ARRAY[G]

Когда объявляется объект типа ARRAY, должен быть задан фактический па­раметр, в данном случае Airplane_Data. В отличие от языков Ada и C++, кото­рые имеют специальный синтаксис для объявления встроенных составных типов, в языке Eiffel все создается из родовых классов с помощью единого на­бора синтаксических и семантических правил.

Обобщения широко используются в языке Eiffel, потому что библиотека содержит определения многих родовых классов, которые вы можете специа­лизировать для своих конкретных требований. Родовые классы также могут быть ограниченными (constrained), чтобы работала модель контракта между родовым классом и его конкретизацией, как это делается в языке Ada (см. раз­дел 10.3). Ограничения задаются не сопоставлением с образцом, а указанием имени класса, для которого фактический родовой параметр должен быть про­изводным. Например, следующий родовой класс может быть конкретизи­рован только типами, производными от REAL:

class Trigonometry[R -> REAL]

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

Наследование

Каждый класс определяет тип, а все классы в системе организованы в одну иерархию. Наверху иерархии находится класс, называющийся ANY. Присваи­вание и равенство определены внутри ANY, но могут быть замещены внутри класса. Синтаксис для наследования такой же, как в языке C++: унаследован­ные классы перечисляются после имени класса. Если задан класс Airplane_Data:

class Airplane_Data

feature

Set_Speed(l: Integer) is...

Get_Speed: Integer is....

feature {}

ID: STRING;

Speed: Integer;

Altitude: Integer;

end; -- class Airplane_Data

его можно наследовать следующим образом:

class SSTJData inherit

Airplane_Data

redefine

Set_Speed, Get_Speed

end

feature

Set_Speed(l: Integer) is...

Get_Speed: Integer is...

feature {}

Mach: Real;

end; — class SST_Data

Все свойства в базовом классе наследуются с их экспортируемыми атрибута­ми в неизменном виде. Однако для производного класса программист может переопределить некоторые или все унаследованные свойства. Переопределя­емые свойства должны быть явно перечислены в redefine-конструкции, кото­рая следует за inherit-конструкцией. Кроме переопределения, свойство можно просто переименовать. Обратите внимание, что унаследованное свойство мо­жет быть реэкспортировано из класса, даже если оно было приватным в базо­вом классе (в отличие от языков C++ и Ada 95, которые не разрешают втор­гаться в ранее скрытую реализацию).

Среда языка Eiffel может отображать плоскую (flat) версию класса, которая показывает все действующие на данный момент свойства, даже если они бы­ли унаследованы и повторно объявлены где-то еще в иерархии. Таким обра­зом, интерфейс класса отчетливо отображается, и программисту не нужно «раскапывать» иерархию, чтобы точно увидеть, что было переобъявлено, а что не было.

Eiffel, аналогично языку C++, но, в отличие от языка Ada 95, использует подход отличимого получателя, поэтому нет необходимости задавать явный параметр для объекта, подпрограмма которого должна быть вызвана:

A: Airplane_Data;

A.Set_Speed(250);

Распределение памяти

В языке EifFel нет никаких явных указателей. Все объекты неявно распределя­ются динамически и доступны через указатели. Однако программист может по выбору объявить объект как расширенный (expanded), в этом случае он бу­дет размещен и доступен без использования указателя:

database: expanded ARRAY[Airplane_Data];

Кроме того, класс может быть объявлен как расширенный, и все его объекты будут доступны непосредственно. Само собой разумеется, что встроенные ти­пы Integer, Character и т.д. являются расширенными.

Обратите внимание, что оператор присваивания или проверки равен­ства

X :=Y;

дает четыре варианта, в зависимости от того, являются объекты X и Y расширенными оба, либо только один из них, либо ни тот ни другой. В языках Ada и C++ программист отвечает за то, чтобы различать, когда подразумева­ется присваивание указателя, а когда — присваивание обозначенных объек­тов. В языке EifFel присваивание прозрачно для программиста, а значение каждого варианта в языке тщательно определено.

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

A: Airplane_Data;

S: SST_Data;

A:=S;

Если распределение было статическим, в объекте А не будет «места» для до­полнительного поля Mach из S. Когда используется косвенное распределе­ние, присваивание — это, по сути, просто копирование указателя. Сравните это с языками Ada 95 и C++, в которых требуются дополнительные понятия: CW-типы и указатели для присваивания, которые поддерживают конкретный тип.

Кроме того, язык Eiffel делает различие между мелким (shallow)и глубоким (deep) копированием в операторах присваивания. При мелком копировании копируются только указатели (или данные, в случае расширенных объектов), в то время как при глубоком копировании копируются структуры данных целиком. Замещая унаследованное определение присваивания, вы можете выбрать любой вариант для любого класса.

Динамический полиморфизм получаем как непосредственное следствие. Возьмем

A.Set_Speed(250);

Компилятор не имеет никакой возможности узнать, является конкретный тип значения, находящегося в данный момент в А, базовым типом Air-plane_Data для А или некоторым типом, порожденным из Airplane_Data. Так как подпрог­рамма Set_Speed была переопределена, по крайней мере, в одном порожден­ном классе, должна выполняться диспетчеризация во время выполнения. Об­ратите внимание, что не требуется никакого специального синтаксиса или се­мантики: все вызовы потенциально динамические, хотя компилятор проведет оптимизацию и использует статическое связывание, где это возможно.

Абстрактные классы

Абстрактные классы в языке Eiffel такие же, как в языках C++ и Ada 95. Класс или свойство в классе может быть объявлено как отсроченное (deferred). Отсроченный класс должен быть сделан конкретным при помощи эффективизации (effecting) всех отсроченных свойств, т. е. предоставления ре­ализации. Обратите внимание, что, в отличие от языков C++ и Ada 95, вы мо­жете объявить объект, чей тип отсрочен; вы получаете null-указатель, который не может использоваться до тех пор, пока ему не будет присвоено значение имеющего силу производного типа:

deferred class Set... -- Абстрактный класс

class Bit_Set inherit Set... -- Конкретный класс

S: Set; -- Абстрактный объект!

В: Bit_Set; - Конкретный объект

!!B; --Создать экземпляр В

S := В; -- Правильно,S получает конкретный объект,

S.Union(...); --который теперь можно использовать

Множественное наследование

Язык Eiffel поддерживает множественное наследование:

class Winged_Vehicle

feature

Weight: Integer;

display is . .. end;

end;

class Motorized_Vehicle

feature

Weight: Integer;

display is ... end;

end;

class Airplane inherit

Winged_Vehicle, Motorized_Vehicle

end;

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

Yandex.RTB R-A-252273-3
Yandex.RTB R-A-252273-4