Руководство по actionscript. часть 2, стр. 004

На русском языке она будет выглядеть следующим образом: Метод интерфейса update из пространства имен LogRecipient не реализован классом LogUI.

Поскольку класс LogUI обещает реализовать методы интерфейса LogRecipient, экземпляры этого класса могут быть использованы везде, где требуется тип данных LogRecipient. Экземпляры класса LogUI фактически принадлежат двум типам данных: LogUI и LogRecipient. Таким образом, даже несмотря на то, что класс LogUI расширяет класс Sprite, экземпляры класса LogUI принадлежат типу LogRecipient и могут благополучно передаваться в метод addRecipient ( ) класса Logger.

Ошибки компилятора — это ключ ко всей системе интерфейсов. Они гарантируют, что класс сдержит свои обязательства по реализации методов интерфейса, тем самым позволив внешнему коду использовать этот класс с уверенностью в его правильном поведении. Эта уверенность особенно важна при разработке приложения, возможности которого будут расширяться другим разработчиком или использоваться третьими лицами.

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

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

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

interface НекоеИмя {

function д/етол! (параметр1:типданных… параметрп-.типданных): типВозвращаемогоЗначения;

function метод2 (параметр1:типданных. . . параметрп-.типданных): типВозвращаемогоЗначения;

function метода (параметр^, типданных… параметрп-.типданных): типВозвращаемогоЗначения;

}

Здесь НекоеИмя — это имя интерфейса, метод1. . . методп — методы данного интерфейса, параметр1: типданных. . . параметрп: типданных — параметры методов, а типВоз — вращаемогоЗначения — тип данных возвращаемого значения каждого из методов.

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

function метод1 (параметр-.типданных)-.типВозвращаемогоЗначения { }

Возникшая ошибка:

Methods defined in an interface must not have a body.

Руководство по actionscript. часть 2, стр. 005

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

Объявления всех методов в интерфейсе не должны включать модификаторы управления доступом. Интерфейсы в языке ActionScript не могут содержать определения переменных; описания интерфейсов не могут быть вложенными. Тем не менее интерфейсы могут включать get — и set-методы, которые могут применяться для имитации переменных (с позиции кода, использующего эти методы). Описания интерфейсов, как и описания классов, могут размещаться либо непосредственно внутри оператора package, либо за его пределами, но нигде больше.

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

class НекоеИмя implements НекийИнтерфейс { }

В данном коде НекоеИмя — это имя класса, который обещает реализовать методы интерфейса НекийИнтерфейс, а НекийИнтерфейс — имя интерфейса. Говорят, что класс НекийКласс «реализует интерфейс НекийИнтерфейс». Обратите внимание, что, если в описании класса используется раздел extends, ключевое слово implements должно идти после него. Более того, если после ключевого слова implement s вместо имени интерфейса вы укажете имя класса, компилятор сгенерирует следующую ошибку:

An interface can only extend other interfaces, but ИмяКласса is a class.

По-русски она будет выглядеть так: Интерфейс может только расширять другие интерфейсы, а ИмяКласса является классом.

Класс НекоеИмя должен реализовать все методы, описанные в интерфейсе НекийИнтерфейс, иначе на этапе компиляции возникнет следующая ошибка:

Interface method имяМетода in namespace ИмяИнтерфейса not implemented by class ИмяКласса.

На русском языке ошибка означает следующее: Метод интерфейса имяМетода в пространстве имен ИмяИнтерфейса не реализован классом ИмяКласса.

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

Interface method имяМетода in namespace ИмяИнтерфейса is implemented with an incompatible signature in class ИмяКласса.

По-русски она будет звучать так: Метод интерфейса имяМетода в пространстве имен ИмяИнтерфейса реализован с несовместимой сигнатурой в классе ИмяКласса.

Руководство по actionscript. часть 2, стр. 006

Класс может реализовать несколько интерфейсов, разделив их имена запятыми, как показано в следующем коде:

class НекоеИмя implements НекийИнтерфейс, НекийДругойИнтерфейс { }

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

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

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

Если класс объявит, что он реализует некий интерфейс, но компилятор не сможет найти этот интерфейс, то появится следующая ошибка:

Interface ИмяИнтерфейса was not found.

На русском языке ошибка будет выглядеть следующим образом: Интерфейс ИмяИнтерфейса не найден.

Соглашения по именованию интерфейсов

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

Предположим, что приложение содержит несколько классов, представляющих визуальные объекты. Некоторые объекты можно перемещать, другие — нет. В нашем проекте объекты, которые могут быть перемещены, должны реализовать интерфейс Moveable. Рассмотрим пример теоретического класса Product Icon, реализующего интерфейс Moveable:

public class Productlcon implements Moveable { public function getPosition ( ):Point {

}

public function setPosition (pos:Point):void { }

}

Интерфейс с именем Moveable обозначает конкретную возможность, которую он добавляет в класс. Объект может быть фрагментом изображения или блоком текста, но, если он реализует интерфейс Moveable, его можно перемещать. Примерами похожих имен являются Storable, Kill able или Serializable. Некоторые разработчики перед именем интерфейса дополнительно указывают букву I, например IMoveable, IKillable или ISerializable.

Руководство по actionscript. часть 2, стр. 007

Наследование интерфейсов

Как и в случае с классами, для наследования одного интерфейса от другого может применяться ключевое слово extends. Например, следующий код демонстрирует интерфейс IntA, который расширяет другой интерфейс — IntB. В данной схеме интерфейс IntB называется подинтерфейсом, а интерфейс IntA — суперинтерфейсом.

public interface IntA { function methodA ( ):void;

}

public interface IntB extends IntA { function methodB ( ):void;

}

Классы, реализующие интерфейс IntB, должны определять не только метод methodB ( ), но и метод methodA ( ). Наследование интерфейсов позволяет описывать иерархию типов, во многом напоминающую иерархию, которая образуется при использовании наследования классов, но без предоставления реализаций методов.

Интерфейсы языка ActionScript также поддерживают множественное наследование, то есть один интерфейс может расширять несколько. Например, рассмотрим следующие три описания интерфейсов:

public interface IntC { function methodC ( ):void;

}

public interface IntD { function methodD ( ):void;

}

public interface IntE extends IntC. IntD { function methodE ( ):void;

}

Поскольку интерфейс IntE расширяет оба интерфейса IntC и IntD, классы, реализующие интерфейс IntE, должны предоставить определения для методов methodC( ), methodD ( ) и methodE ( ).

Руководство по actionscript. часть 2, стр. 008

Интерфейсы-маркеры

Чтобы быть полезными, интерфейсы могут вообще не содержать никаких методов. Иногда пустые интерфейсы, называемые интерфейсами-маркерами, применяются для «отметки» (обозначения) класса, обладающего некоторой возможностью. Требования, предъявляемые к отмеченным классам (классам, реализующим интерфейс-маркер), описываются в документации по каждому конкретному интерфейсу-маркеру. Например, API среды выполнения Flash включает интерфейс-маркер IBitmapDrawable, обозначающий класс, который может быть отображен объектом BitmapData. Класс BitmapData будет отображать только те классы, которые реализуют интерфейс IBitmapDrawable (хотя на самом деле этот интерфейс не определяет никаких методов). Интерфейс IBitmapDrawable используется просто для того, чтобы «показать», что данный класс пригоден для работы с растровым изображением. Вот исходный код интерфейса IBitmapDrawable:

package flash. display { interface IBitmapDrawable { }

}

Другой пример использования составных типов

Как уже известно из предыдущего примера с протоколирующим классом, класс может не только наследоваться от другого класса, но и реализовывать интерфейс. Экземпляры подкласса одновременно принадлежат типу данных суперкласса и типу данных интерфейса. Например, экземпляры класса LogUI из рассмотренного примера принадлежали типам данных Sprite и LogRecipient, поскольку класс LogUI был унаследован от класса Sprite и реализовывал интерфейс LogRecipient. Рассмотрим эту важную архитектурную структуру на новом примере.

**4

Для понимания материала, изложенного в этом разделе, требуется предварительное знание массивов (упорядоченных списков значений), которые еще не рассматривались Эа1 в этой книге. Если вы незнакомы с массивами, то пока пропустите этот раздел и вернитесь к нему после изучения гл. 11.

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

Одним из классов нашего приложения является простой класс Rectangle с переменными экземпляра width, height, fillColor и lineColor. Для представления объектов Rectangle в виде строк класс Rectangle реализует метод serialize( ), который возвращает строку следующего формата:

«wi <№\=значение | hei ф^значение | f i 11 col ог=значение \ 1 i necol ог=значение"

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

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

Руководство по actionscript. часть 2, стр. 009

Когда наступит время сохранить состояние нашего приложения, экземпляр пользовательского класса StorageManager выполнит следующие задачи.

1. Соберет объекты для сохранения.

2. Преобразует каждый объект в строку (вызвав метод serialize( )).

3. Перенесет объекты на диск.

Чтобы гарантировать тот факт, что каждый сохраняемый объект может быть се-риализован, класс StorageManager отклонит любые экземпляры классов, которые не принадлежат типу данных Serializable. Вот фрагмент кода класса StorageManager, демонстрирующий метод addOb j ееt ( ), который используется объектами для регистрации в списке сохраняемых объектов (обратите внимание, что в этот метод могут быть переданы только экземпляры, принадлежащие типу Serializable):

package { public class StorageManager { public function addObject (o:Serializable):void { }

}

}

Тип данных Serializable описывается одноименным интерфейсом, который содержит один-единственный метод serialize ( ), как показано в следующем коде:

package { public interface Serializable { function serialize( ) .-String;

}

}

Для выполнения сериализации создадим класс Serialize г, реализующий интерфейс Serializable. Этот класс предоставляет следующие базовые методы для сериализации любого объекта:

? setSerializationObj ( )— указывает объект для сериализации;

? setSerializationVars ( ) — задает, какие переменные объекта должны быть сериализованы;

? setRecordSeparator ( ) — указывает строку, используемую в качестве разделителя между переменными;

? serialize( ) — возвращает строку, представляющую объект.

Руководство по actionscript. часть 2, стр. 010

Рассмотрим исходный код класса Serializer: package {

public class Serializer implements Serializable { private var serializationVars:Array; private var serializationObj:Serializable; private var recordSeparator:String;

public function Serializer ( ) { setSeri alizati onObj(thi s);

}

public function setSerializationVars (vars:Array):void { serializationVars = vars;

}

public function setSerializationObj (obj:Serializable):void { serializationObj = obj;

}

public function setRecordSeparator (rs:String):void { recordSeparator = rs;

}

public function serialize ( ):String { var s:String = «»;

// Обратите внимание, что счетчик цикла // уменьшается до нуля.

// а его обновление (декремент i) происходит внутри // условного выражения цикла

for (var i:int = serializationVars. length; —i >= 0; ) { s += serializationVars[i]

+ «=» + String(serializationObj[serializationVars[i]]); if (i > 0) { s += recordSeparator;

}

}

return s; } ‘

}

}

Если какой-либо класс желает воспользоваться возможностями сериализации класса Serializer, то может просто расширить его. Класс, непосредственно расширивший класс Serializer, унаследует и интерфейс Serializable, и реализацию этого интерфейса классом Serializer.

Руководство по actionscript. часть 2, стр. 011

Обратите внимание на общую структуру системы сериализации: класс Serializer реализует интерфейс Serializable, предоставляя базовую реализацию, которая может быть использована в других классах через процедуру наследования. Но при этом классы могут реализовать интерфейс Serializable самостоятельно, предоставив желаемое поведение для метода serialize ( ).

Например, следующий код демонстрирует класс Point, определяющий переменные х и у, которые должны быть сериализованы. Этот класс расширяет класс Serializer и непосредственно использует его возможности.

package {

public class Point extends Serializer { public var x:Number; public var y:Number;

public function Point (x:Number. y:Number) { super( );

setRecordSeparator(«.»); setSerializationVars(["x". "y"]);

this. x = x; this. у = у;

}

}

}

Код, желающий сохранить экземпляр класса Point на диск, просто вызывает метод serialize ( ) над этим экземпляром, как показано в следующем примере:

var p:Point = new Point(5. 6);

trace(p. serialize( )); // Отображает: y=6,x=5

Стоит отметить, что класс Point непосредственно не реализует интерфейс Serializable. Этот класс расширяет класс Serializer, который, в свою очередь, реализует интерфейс Serializable.

Руководство по actionscript. часть 2, стр. 012

Класс Point не расширяет никакой другой класс, поэтому он может свободно расширить класс Serializer. Тем не менее, если некий класс желает использовать класс Serializer, но уже расширяет другой класс, вместо наследования необходимо применять композицию. Иными словами, вместо расширения класса Serializer класс непосредственно реализует интерфейс Serializable, сохранит объект Serializer в переменной экземпляра и переадресует вызовы метода serializer ( ) этому объекту. Например, рассмотрим код упомянутого ранее класса Rectangle. Этот класс расширяет класс Shape, но использует возможности класса Serializer через композицию (обратите особое внимание на строки, выделенные полужирным шрифтом):

// Суперкласс Shape package { public class Shape {

public var fillColonuint = OxFFFFFF;

public var lineColor:uint = 0;

public function Shape (fillColonuint. lineColor:uint) { this. fillColor = fi11 Col or: this. lineColor = lineColor;

}

// Класс Rectangle package {

// Подкласс Rectangle непосредственно реализует // интерфейс Serializable

public class Rectangle extends Shape implements Serializable { public var width:Number = 0; public var height:Number = 0; private var serializer:Serializer:

public function Rectangle (fil 1 Col or :uint. lineColonuint) { super(fillColor, lineColor)

// Именно здесь создается композиция

serializer = new Serializer( );

seri ali zer. setRecordSeparator(«|»);

seri alizer. setSeri alizationVars(["height", "width",

"fillColor". "lineColor"]); seri ali zer. setSeri ali zati onObj(this);

}

public function setSize (w:Number. h:Number):void { width = w; height = h:

}

public function getArea ( ):Number { return width * height;

}

public function serialize ( ):String { // Здесь класс Rectangle переадресует вызов метода serialize( ) // экземпляру класса Serializer, сохраненному // в переменной serializer

return serializer. serialize( );

}

}

}

Как и в случае с классом Point, код, желающий сохранить экземпляр класса Rectangle, просто вызывает метод serialize ( ) над этим объектом. Вызов метода через композицию будет переадресован экземпляру класса Serializer, сохраненному в классе Rectangle. Вот пример использования этого класса:

var r:Rectangle = new Rectangle(0xFF0000. OxOOOOFF); r. setSizedO. 15);

// Отображает: 1ineColor=2551fi11 Color=167116801width=101height=15 trace(r. serialize( ));

Если класс желает реализовать собственную версию метода serialize ( ) вместо того, чтобы использовать базовую версию этого метода, предоставляемую классом Serializer, он должен непосредственно реализовать интерфейс Serializable, предоставив определение и само тело метода serialize( ).

Руководство по actionscript. часть 2, стр. 013

Разделение интерфейса типа данных Serializable и его реализации позволяет любому классу легко выбрать один из следующих вариантов реализации метода

serialize ( ):

? расширить класс Serializer;

? использовать класс Serializer через композицию;

? непосредственно предоставить собственную реализацию метода serialize( ).

Если класс не расширяет другой класс, он может расширить класс Serializer (этот вариант наименее трудоемкий). Если класс уже расширяет другой класс, он может использовать возможности класса Serializer через композицию (этот вариант наиболее гибкий). Наконец, если класс нуждается в собственной уникальной процедуре сериализации, он может непосредственно реализовать интерфейс Serializable (этот вариант наиболее трудоемкий, но в некоторых ситуациях обойтись без него невозможно).

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

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

НекийАбстрактныйТип

Абстрактное описание типа данных

Реализует

j»"»"! Интерфейс I | Класс

Реализует

НекийКонкретныйТип

Реализация типа данных

Расширяет

Сохраняет

НекийКонкретныйПодтип

НекийАбстрактныйТип

используется через наследование

НекийДругойТип

Несвязанный класс

Т

Расширяет

)

НекийДругойПодтип

НекийАбстрактныйТип используется через композицию

Экземпляр НекийКонкретныйТип

Рис. 9.1. Множественное наследование типов данных через интерфейсы

На рис. 9.2 показана структура конкретных классов Serializable, Point и Rectangle из предыдущего примера.



Полезные ссылки
Случайные записи
  • 17.03.2011">Руководство по actionscript. часть 2, стр. 152
  • 17.03.2011">Руководство по actionscript. часть 2, стр. 147
  • 14.06.2010">Самоучитель по креативному веб-дизайну. Книга 4, стр.63
  • 21.06.2011">Как правильно оптимизировать сайт для социальных сетей
  • 24.02.2011">Руководство по actionscript. часть 6, стр. 107
  • 15.11.2011">Обзор ноутбука Lenovo
  • 04.06.2010">Самоучитель по креативному веб-дизайну. Книга 3, стр.33
  • 23.01.2011">Руководство по actionscript. часть 1, стр. 095
  • 11.05.2010">Самоучитель по креативному веб-дизайну. Книга 1, стр.34
  • 14.03.2011">Руководство по actionscript. часть 3, стр. 080
  • 06.03.2011">Руководство по actionscript. часть 4, стр. 133
  • 22.08.2011">Конец прекрасной эпохи
  • 03.06.2010">Самоучитель по креативному веб-дизайну. Книга 3, стр.39
  • 13.06.2010">Самоучитель по креативному веб-дизайну. Книга 4, стр.92
  • 10.03.2011">Руководство по actionscript. часть 4, стр. 028
Опрос

Какие цвета вы предпочитаете?

View Results

Loading ... Loading ...