Статьи

AS3 101: ООП — Введение в интерфейсы

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

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


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

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


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

Представьте себе, если хотите, музыкальную студию с большим количеством электронного оборудования (для наших целей, чем больше дискретных частей оборудования, тем лучше; обратите внимание на все это в музыкальной шкатулке).

Музыкальная студия с большим количеством оборудования

Фотография от пользователя Flickr The Cowshed и распространяется под лицензией Creative Commons Attribution-NoDerivs 2.0 Generic.

В конце концов, все эти разрозненные производители шума должны быть направлены на регистраторы шума, а затем запись воспроизводится и записывается в виде микса. Из точки А в точку В в точку С должно быть много звука, а между ними — множество точек.

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

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

К счастью, есть несколько стандартов, которых придерживаются все производители. Это немного грубое упрощение, но почти все аудиоразъемы, которые вы найдете в музыкальной студии, будут одного из двух видов: XLR или четверть дюйма (аудиофилы, давайте проигнорируем импеданс и балансировку, а также другие разъемы, такие как RCA). ). К счастью, используя только два типа кабеля и дополнительный адаптер, вы можете подключить практически любой аудиоустройство к любому другому.

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

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

Фотография от пользователя Flickr The Cowshed и распространяется под лицензией Creative Commons Attribution-NoDerivs 2.0 Generic.


Так же, как и аудиооборудование, интерфейс объекта — это то, как он соединяется с внешним миром. Теперь «соединение», вероятно, не лучшее слово для использования. Давайте использовать вместо этого «делает себя доступным».

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

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

Вы, наверное, слышали об аббревиатуре «API». В наши дни это довольно модно в виде API-интерфейсов веб-служб, таких как Flickr API или Google Maps API. API означает « Интерфейс прикладного программирования ». В этом смысле интерфейс — это набор методов, которые вы можете вызывать в веб-сервисе, определяющий не только имя методов, но и параметры, которые вы можете отправить ему, и тип данных, которые будут возвращены.


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

Точно так же важно понимать тонкое различие между интерфейсом, видимым из внешнего мира, и реализацией . Таким образом, интерфейс определяет сигнатуру метода (сигнатура метода обозначает имя метода, его аргументы и тип, а также тип возвращаемого значения), например: function publicMethod(arg1:String):Boolean . Однако то, что на самом деле происходит внутри метода, не касается интерфейса. Это до реализации.

Если вам нравилась аналогия с аудиооборудованием, вы можете думать об этом различии между интерфейсом и реализацией как о разнице между надеванием 1/4-дюймового разъема на деталь механизма и компонентами и качеством материала, вставляемого в этот разъем. Не все 1/4-дюймовые гнезда созданы равными; некоторые позолоченные, а другие никелированные. У некоторых будут припаяны высококачественные провода, другие будут отлиты непосредственно на печатную плату. Реализация (материалы и метод изготовления) не входит в юрисдикцию интерфейса (разъем шириной 1/4 дюйма).

Если вам не понравилась аналогия с аудиооборудованием, прошу прощения за то, что поднял ее снова.


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

Смущены еще? Если нет, вы не думаете об этом достаточно сложно! Просто дразнить, но я не ожидаю, что это будет иметь полный смысл на данный момент. Давайте перейдем к простому, возможно, бесполезному, но определенно практическому примеру.


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

Теперь создайте новый файл .as. Я предполагаю, что вы используете редактор, который не имеет большого количества шаблонов, но если вы используете Flash Builder, вы можете получить большую часть этого, выбрав «Интерфейс ActionScript» в меню нового файла. И, конечно же, многие другие редакторы имеют шаблоны (даже Flash CS5 имеет шаблон интерфейса ActionScript 3.0). Но хорошо изучить гайки и болты этих шаблонов.

Сохраните ваш файл как « ITest.as » в папке проекта. Это не обязательно, но обычной практикой является присвоение имен вашим интерфейсам в соответствии с соглашением об именовании классов (т. Е. Заглавной первой буквой, после верблюда), с добавлением «I» на передней панели. «I» указывает, что тип данных является интерфейсом, а не обычным классом. Flash с радостью будет работать с интерфейсами, которые названы без начального «Я», но это соглашение, которое вы найдете удобным для ваших собственных проектов, а также полезно при просмотре классов и интерфейсов других разработчиков.

Теперь введите следующий код котельной плиты:

1
2
3
4
5
package {
    public interface ITest {
 
    }
}

Вы только что написали интерфейс (тот, который мало что определяет, но все же). Обратите внимание, что если бы мы хотели использовать структуру пакета, мы бы расшифровали ее после ключевого слова package как в классах. И за исключением использования interface , определение идентично определению класса. Однако обратите внимание, что мы не добавляем конструктор. Конструктор — это специальный метод, который не является частью интерфейса. Любой другой метод, однако, мы можем определить в интерфейсе, и мы сделаем это дальше. Добавьте выделенную строку в файл интерфейса:

1
2
3
4
5
package {
    public interface ITest {
        function test():void;
    }
}

Теперь мы объявили, что интерфейс ITest имеет один публичный метод. Он называется test , не принимает аргументов и ничего не возвращает. Что оно делает? Интерфейс не очень связан с этим. То, что он делает, будет зависеть от конкретной реализации. Обратите внимание, что сигнатура метода заканчивается точкой с запятой, а не фигурными скобками.

Также обратите внимание на отсутствие public или private или чего-либо перед function . Исключительно сообразительный будет помнить, что пропуск модификатора доступа в классе по умолчанию будет internal . Поскольку интерфейсы определяют только открытые методы, по умолчанию используется public . На самом деле, даже попытка указать public приведет к ошибкам.

Сохраните ваш файл, и ваш интерфейс теперь можно использовать. Мы воспользуемся этим через минуту.


Чтобы применить предыдущие практические инструкции в теории, механика интерфейса в ООП довольно похожа на класс:

  • Интерфейс определен в своем собственном текстовом (.as) файле, как класс.
  • Код плиты котла почти идентичен коду файла класса; Вы используете пакеты и импортируете по мере необходимости.
  • Имя интерфейса должно совпадать с базовым именем файла.
  • Интерфейс является типом данных, как класс.

Есть, как и следовало ожидать, различия. Самая большая разница заключается в следующем:

Интерфейсы не имеют никакой функциональности.

Существуют и другие отличия, некоторые из которых мы еще не затронули:

  • В табличке вы используете interface ключевых слов вместо class
  • Вы можете определить только публичные методы
    • Это включает неявные сеттеры и геттеры — например, function get someString():String
    • Это не включает частные, защищенные или внутренние методы.
    • Это не включает в себя какие-либо свойства.
  • При определении этих открытых методов вы не используете ключевое слово public .
    • Предполагается, что public не только является компилируемым кодом для использования этого ключевого слова в сигнатуре метода.
  • Видя, что функциональность отсутствует, вы заканчиваете объявление метода точкой с запятой и опускаете фигурные скобки, которые обычно
    обернуть вокруг реализации метода.

Недостаточно просто определить интерфейс; интерфейс должен быть реализован, чтобы быть полезным. Что реализует интерфейс? Класс, естественно. Любой класс, который вы пишете, может технически реализовать любой интерфейс, который вы выберете. Мы посмотрим, как это работает на этом этапе.

Этот класс часто называют «реализацией», когда упоминается относительно интерфейса. Его также можно назвать «конкретным» классом; в отличие от нефункционального интерфейса, это нечто «твердое» и «реальное».

Создайте новый текстовый файл с именем « TestImplementation.as » и сохраните его в папке проекта. Это будет класс, поэтому добавьте следующий базовый класс скелета:

1
2
3
4
5
6
package {
    public class TestImplementation {
        public function TestImplementation() {
        }
    }
}

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

1
2
3
4
5
6
package {
    public class TestImplementation implements ITest {
        public function TestImplementation() {
        }
    }
}

После этого мы объявили, что наш класс TestImplementation будет реализовывать интерфейс ITest . Но все, что мы сделали, это объявили это намерение. Нам нужно на самом деле сделать реализацию. Добавьте метод test объявленный ITest в наш класс TestImplementation :

01
02
03
04
05
06
07
08
09
10
package {
    public class TestImplementation implements ITest {
        public function TestImplementation() {
        }
 
        public function test():void {
            trace(«Just a little test.»);
        }
    }
}

На данный момент наш класс полностью реализует интерфейс ITest . Наш следующий шаг — увидеть этот класс в действии.


Создайте файл Flash ActionScript 3.0 («Файл»> «Создать»> «ActionScript 3.0») и сохраните его как «first-interface.fla» в папке проекта.

Создайте еще один текстовый файл с именем «Main.as» в папке вашего проекта. Это будет наш класс документов. Перед записью содержимого класса вернитесь в файл Flash и введите «Основной» в качестве класса документа на панели «Свойства» для документа.

Теперь в Main.as введите следующее:

01
02
03
04
05
06
07
08
09
10
package {
    import flash.display.Sprite;
 
    public class Main extends Sprite {
        public function Main() {
            var tester:TestImplementation = new TestImplementation();
            tester.test();
        }
    }
}

Это неудивительное использование нашего класса TestImplementation . Мы создаем его новый экземпляр, затем вызываем для него метод, что приводит к трассировке на панели «Вывод».

Результат запуска тест-интерфейса FLA

Этот готовый проект доступен в пакете загрузки в папке «first-interface».

Теперь давайте рассмотрим взаимосвязь между интерфейсом и реализацией.


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

Давайте начнем с некоторых модификаций интерфейса. Откройте ITest.as и, во-первых, давайте добавим к нему метод:

1
2
3
4
5
6
package {
    public interface ITest {
        function test():void;
        function anotherTest():void;
    }
}

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

1
[…]/TestImplementation.as, Line 2 1044: Interface method anotherTest in namespace ITest not implemented by class TestImplementation.

Эта ошибка говорит, в довольно неестественном компьютерном жаргоне, что мы не смогли поддержать заключенный нами контракт. Мы сказали, что реализуем интерфейс ITest , но никогда не реализовывали метод anotherTest .

Давайте попробуем исправить эту проблему. Вернувшись в TestImplementation.as , добавьте соответствующий метод, но мы намеренно допустим ошибку в иллюстративных целях:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
package {
    public class TestImplementation implements ITest {
        public function TestImplementation() {
        }
 
        public function test():void {
            trace(«Just a little test.»);
        }
 
        public function anotherTest(input:String):void {
            trace(«Another test: » + input);
        }
    }
}

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

1
[…]/TestImplementation.as, Line 2 1144: Interface method anotherTest in namespace ITest is implemented with an incompatible signature in class TestImplementation.

Это связано с использованием input аргумента в нашей реализации метода anotherTest . Это не объявлено в интерфейсе, поэтому у нас есть «несовместимая подпись». Если вы очистите это:

1
2
3
public function anotherTest():void {
    trace(«Another test»);
}

Вы увидите, что теперь мы можем успешно скомпилировать. Урок здесь заключается в том, что интерфейсы довольно строгие. Как только вы говорите, что реализуете один, вы обязаны подписать контракт, чтобы реализовать каждый метод точно так, как объявлено интерфейсом. В противном случае ваш SWF-файл не будет работать из-за ошибок компилятора.


Теперь обратите внимание на важный аспект этого: интерфейс определяет только минимум методов, которые должны присутствовать в вашем классе реализации. То есть, поскольку наш класс TestImplementation реализует ITest , мы должны по крайней мере иметь метод test метод anotherTest . Но интерфейс не имеет юрисдикции над тем, какие другие методы могут быть объявлены. Мы можем определить столько других свойств и методов, сколько захотим, при условии, что anotherTest методы test и anotherTest .

Чтобы изучить это, мы добавим третий метод в TestImplementation.as :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
package {
    public class TestImplementation implements ITest {
        public function TestImplementation() {
        }
 
        public function test():void {
            trace(«Just a little test.»);
        }
 
        public function anotherTest():void {
            trace(«Another test»);
        }
 
        public function extraneousTest():void {
            trace(«One more test.»);
        }
    }
}

Опять же, этот метод не определен в ITest . Если вы запустите фильм сейчас, вы не увидите никаких изменений. Все компилируется просто отлично. У нашей реализации просто есть дополнительный метод.


Теперь для чего-то более хитрого. Я упоминал ранее, что интерфейс, как и класс, представляет собой тип данных, доступный в ActionScript. Мы можем проверить это, внеся небольшое изменение в файл Main.as :

1
2
3
4
public function Main() {
    var tester:ITest = new TestImplementation();
    tester.test();
}

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

1
2
3
4
5
public function Main() {
    var tester:ITest = new TestImplementation();
    tester.test();
    tester.anotherTest();
}

И это будет работать так, как вы ожидаете.

Панель «Вывод», показывающая две трассы от двух вызовов метода

Однако добавление третьего вызова метода выглядит так:

1
2
3
4
5
6
public function Main() {
    var tester:ITest = new TestImplementation();
    tester.test();
    tester.anotherTest();
    tester.extraneousTest();
}

Это приведет к следующей ошибке компилятора:

1
[…]/Main.as, Line 9 1061: Call to a possibly undefined method extraneousTest through a reference with static type ITest.

Если вы ниндзя ActionScript, вы увидите, что здесь происходит. В переменной tester хранится экземпляр TestImplementation . TestImplementation определен метод extraneousTest . На первый взгляд, мы можем предположить, что tester сможет выполнить метод extraneousTest .

Тем не менее, переменная tester имеет тип данных ITest (не как TestImplementation ). ITest не объявляет метод extraneousTest . Поэтому при оценке использования переменной tester компилятор ищет допустимые действия в интерфейсе ITest , а не в классе TestImplementation . Поэтому, когда компилятор встречает вызов extraneousTest , он понимает, что extraneousTest не был определен в ITest и выдает вышеуказанную ошибку.

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

Теперь, зачем нам это делать? Читай дальше.


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

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

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

Конечно, нам нужно определить слово. Это самое странное из всех слов, которые мы встречали до сих пор. Это от греческого: poly означает «многие» и morph означает «форма». «Много форм».

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

Теперь, это, вероятно, не имеет большого смысла сейчас. Итак, давайте перейдем к более практической иллюстрации с новым, но все же простым примером.


Мы создадим совершенно новый проект для этой иллюстрации. Вы можете следовать сокращенным подэтапам, перечисленным в этом шаге, или вы можете просто открыть проект «language-start», найденный в пакете загрузки.

  1. Создайте новую папку проекта.
  2. Создайте в этой папке новый FLA AS3 с именем language.fla .
  3. Создайте новый класс документа для этого FLA с именем Main.as. Это код для входа в него:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    package {
        import flash.display.Sprite;
        public class Main extends Sprite {
            public function Main() {
                var english:IPhrases = new EnglishPhrases();
                trace(english.greeting());
                trace(english.urgentSituation());
            }
        }
    }
  4. Создайте новый файл интерфейса под названием « IPhrases.as ». Это код для этого файла:

    1
    2
    3
    4
    5
    6
    package {
        public interface IPhrases {
            function greeting():String;
            function urgentSituation():String;
        }
    }
  5. Создайте еще один файл с именем « EnglishPhrases.as » и введите этот код:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    package {
        public class EnglishPhrases implements IPhrases {
            public function EnglishPhrases() {
            }
            public function greeting():String {
                return «Hello.»;
            }
            public function urgentSituation():String {
                return «Where is the bathroom?»;
            }
        }
    }

Пройдите тестирование фильма, чтобы убедиться, что все в порядке. Вы должны получить две трассы на панели «Вывод»:

Приветствие и срочный вопрос на панели «Вывод»

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

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

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

Наконец, кроме двух изменений, проект концептуально идентичен тому, что мы сделали до сих пор; у нас есть интерфейс, и одна реализация этого интерфейса. Затем файл Flash и его класс документа устанавливают переменную, типизированную для интерфейса, и создают экземпляр класса в переменной. Затем в экземпляре вызываются два метода.

Теперь по волшебству.


Наша цель — добавить еще один класс. Этот класс также будет реализацией IPhrases . Оба будут мирно сосуществовать; нет взрыва вселенной или чего-либо еще. Еще.

Создайте еще один текстовый файл в папке вашего проекта, который называется « SpanishPhrases.as ». Добавьте этот код в класс:

01
02
03
04
05
06
07
08
09
10
11
12
package {
    public class SpanishPhrases implements IPhrases {
        public function SpanishPhrases() {
        }
        public function greeting():String {
            return «Hola.»;
        }
        public function urgentSituation():String {
            return «¿Dónde está el baño?»;
        }
    }
}

(Обратите внимание, что если вы используете файл, предоставленный в пакете загрузки, он был сохранен в кодировке UTF-8. Возможно, вам придется переместить текстовый редактор, если эти символы, не входящие в ASCII (например, ¿и ñ), отображаются странным образом)

Как видите, мы заключаем договор, написав implements IPhrases . А затем мы приступаем к реализации методов, определенных в IPhrases . Наша реализация здесь похожа, но уникальна для EnglishPhrases . Итак, теперь у нас есть две конкретные реализации IPhrases в нашем проекте. Давайте попробуем их использовать. В Main.as добавьте немного кода:

1
2
3
4
5
6
7
8
9
public function Main() {
    var english:IPhrases = new EnglishPhrases();
    trace(english.greeting());
    trace(english.urgentSituation());
 
    var spanish:IPhrases = new SpanishPhrases();
    trace(spanish.greeting());
    trace(spanish.urgentSituation());
}

Запустите Flash-фильм, и вы получите удвоенный результат:

Панель вывода, показывающая результат всех этих следов

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


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

Начнем с жесткого кодирования конкретных типов. Верните ваш файл Main.as к этому:

1
2
3
4
5
public function Main() {
    var phrases:IPhrases = new EnglishPhrases();
    trace(phrases.greeting());
    trace(phrases.urgentSituation());
}

Запустите его, и результаты должны быть предсказуемы.

Теперь измените строку 6, чтобы вместо создания нового экземпляра SpanishPhrases мы создали экземпляр SpanishPhrases :

1
2
3
4
5
public function Main() {
    var phrases:IPhrases = new SpanishPhrases();
    trace(phrases.greeting());
    trace(phrases.urgentSituation());
}

И запустите это снова. Следует ожидать результатов, но я хочу, чтобы вы сосредоточились на том, как единственное, что мы только что изменили, — это имя класса после new ключевого слова. Переменная не изменилась, наиболее важно тип данных переменной. Если бы мы использовали EnglishPhrases в качестве типа данных, то первый пример сработал бы, а второй — нет. Компилятор будет жаловаться на попытку поместить объект SpanishPhrases в переменную, типизированную как EnglishPhrases .

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


Давайте сделаем еще один шаг вперед и создадим базовый пользовательский интерфейс, который позволяет пользователю вызывать greeting и urgentSituation нажатия кнопок.

Мы могли бы нарисовать некоторые кнопки во Flash и работать с пользовательским интерфейсом таким образом, но давайте воспользуемся возможностью воспользоваться возможностью повторного использования классов и некоторой работой, которую мы проделали ранее. Если вы следовали Button101 по ООП, вы создали класс Button101 . Если нет, или если вы последовали, но не сохранили работу, вы можете найти класс в пакете загрузки.

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

С классом Button101 доступным для языкового проекта, мы можем настроить наш интерфейс с кодом в Main.as. Мы начнем с этого класса, поэтому просто замените текущее содержимое следующим:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package {
    import flash.display.Sprite;
    import flash.events.MouseEvent;
 
    public class Main extends Sprite {
 
        private var _phrases:IPhrases;
 
        public function Main() {
            _phrases = new EnglishPhrases();
 
            var test1Button:Button101 = new Button101();
            test1Button.label = «greeting()»;
            test1Button.x = 10;
            test1Button.y = 10;
            addChild(test1Button);
            test1Button.addEventListener(MouseEvent.CLICK, doGreeting);
 
            var test2Button:Button101 = new Button101();
            test2Button.label = «urgentSituation()»;
            test2Button.x = 10;
            test2Button.y = 100;
            addChild(test2Button);
            test2Button.addEventListener(MouseEvent.CLICK, doUrgentSituation);
 
        }
 
        private function doGreeting(e:MouseEvent):void {
            trace(_phrases.greeting());
        }
 
        private function doUrgentSituation(e:MouseEvent):void {
            trace(_phrases.urgentSituation());
        }
    }
}

Во-первых, обратите внимание, что мы создали свойство для хранения объекта IPhrases , и мы создаем его как объект EnglishPhrases в конструкторе. Мы создаем свойство, чтобы этот объект сохранялся, и мы можем ссылаться на него, когда пользователь нажимает кнопки.

Во-вторых, основная часть кода посвящена созданию двух кнопок, которые подключаются к двум прослушивателям событий щелчка. Эти слушатели вызывают один из двух методов объекта IPhrases .

Идите и проверьте это сейчас; вы увидите что-то вроде следующего:

SWF работает в IDE

И если вы нажмете кнопки, вы получите следы.

Панель «Вывод» после нескольких нажатий на кнопки

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


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

Мы собираемся добавить еще две кнопки в пользовательском интерфейсе. В Main.as в конструктор добавьте выделенный код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public function Main() {
    _phrases = new EnglishPhrases();
 
    var test1Button:Button101 = new Button101();
    test1Button.label = «greeting()»;
    test1Button.x = 10;
    test1Button.y = 10;
    addChild(test1Button);
    test1Button.addEventListener(MouseEvent.CLICK, doGreeting);
 
    var test2Button:Button101 = new Button101();
    test2Button.label = «urgentSituation()»;
    test2Button.x = 10;
    test2Button.y = 100;
    addChild(test2Button);
    test2Button.addEventListener(MouseEvent.CLICK, doUrgentSituation);
 
    var englishButton:Button101 = new Button101();
    englishButton.label = «English»;
    englishButton.x = 300;
    englishButton.y = 10;
    addChild(englishButton);
    englishButton.addEventListener(MouseEvent.CLICK, useEnglish);
 
    var spanishButton:Button101 = new Button101();
    spanishButton.label = «Español»;
    spanishButton.x = 300;
    spanishButton.y = 100;
    addChild(spanishButton);
    spanishButton.addEventListener(MouseEvent.CLICK, useSpanish);
}

Теперь нам нужно написать два новых обработчика событий. После метода doUrgentSituation добавьте эти методы:

1
2
3
4
5
6
7
private function useEnglish(e:MouseEvent):void {
    _phrases = new EnglishPhrases();
}
 
private function useSpanish(e:MouseEvent):void {
    _phrases = new SpanishPhrases();
}

И попробуй это. Вы увидите что-то вроде этого:

Пользовательский интерфейс с четырьмя кнопками

Все начинается на английском языке, и нажатие оригинальных двух кнопок будет таким же, как и раньше. Но попробуйте нажать на испанскую кнопку, а затем на кнопки «приветствие ()» и «срочный переход ()». Заметили что-нибудь другое?

Панель «Вывод» после нажатия на кнопку «Испанский»

Давайте немного поговорим о теории. Вы можете все еще задаться вопросом, почему у нас возникли проблемы с созданием интерфейса, созданием реализаций и настройкой логики пользовательского интерфейса для выполнения такой простой задачи. После всего этого был бы достигнут тот же конечный результат, все в классе Main (с помощью класса Button101 ):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Main extends Sprite {
 
    private var _language:String;
 
    public function Main() {
        _language = «en»;
 
        var test1Button:Button101 = new Button101();
        // Set up the rest of the buttons as before…
    }
 
    private function doGreeting(e:MouseEvent):void {
        switch (_language) {
            case «en»:
                trace(«Hello.»);
                break;
            case «es»:
                trace(«Hola.»);
                break;
        }
    }
 
    private function doUrgentSituation(e:MouseEvent):void {
        switch (_language) {
            case «en»:
                trace(«Where is the bathroom?»);
                break;
            case «es»:
                trace(«¿Dónde está el baño?»);
                break;
        }
    }
 
    private function useEnglish(e:MouseEvent):void {
        _language = «en»;
    }
 
    private function useSpanish(e:MouseEvent):void {
        _language = «es»;
    }
}

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

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

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

Самая веская причина в моей памяти — безопасность типов . Подумайте, что нужно для расширения лингвистических возможностей этой маленькой программы. Чтобы добавить язык, вам нужно будет войти в каждый оператор switch котором определяется локализованная строка, и добавить регистр, стараясь точно _language свойства _language . Чтобы добавить строку, вам необходимо добавить новый оператор switch , обеспечивающий учет всех существующих языков (и, опять же, правильное написание).

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

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

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

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

Не только это, но и создание меньших классов позволяет нам легче разделить эту ответственность с другими объектами; если нам понадобится строка «приветствия» где-то, кроме Main , было бы легко обеспечить доступ к объектам IPhrases и знать, что логика IPhrases .

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


Мы снова начнем с нового проекта. Как и в примере с языком, вы можете открыть проект «debugger-start» (в пакете загрузки) или выполнить следующие сжатые шаги:

  1. Создайте новую папку для проекта с именем отладчик .
  2. Создайте FLA-файл AS3 и сохраните его в папке проекта как debugger.fla .
  3. Создайте класс документа для FLA и сохраните его как Main.as. Убедитесь, что FLA настроен на использование Main в качестве класса документа. Добавьте этот код в файл:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package {
        import flash.display.*;
        import flash.events.*;
        import flash.system.*;
        public class Main extends Sprite {
            public function Main() {
                // Fill this in later.
                var message1Button:Button101 = new Button101();
                message1Button.label = «Message 1»;
                message1Button.x = 10;
                message1Button.y = 10;
                addChild(message1Button);
                message1Button.addEventListener(MouseEvent.CLICK, message1);
                 
                var message2Button:Button101 = new Button101();
                message2Button.label = «Message 2»;
                message2Button.x = 10;
                message2Button.y = 100;
                addChild(message2Button);
                message2Button.addEventListener(MouseEvent.CLICK, message2);
            }
            private function message1(e:MouseEvent):void {
                // Fill this in later.
            }
            private function message2(e:MouseEvent):void {
                // Fill this in later.
            }
        }
    }
  4. Создайте файл интерфейса, сохраненный как « IDebugger.as » в папке проекта. Это код:

    1
    2
    3
    4
    5
    package {
        public interface IDebugger {
            function out(message:*):void;
        }
    }
  5. Создайте файл класса с именем « Trace.as » и сохраните его в папке проекта со следующим кодом (который будет расширен в ближайшее время):

    1
    2
    3
    4
    5
    6
    7
    package {
        public class Trace implements IDebugger {
            public function out(message:*):void {
                // Fill this in later
            }
        }
    }
  6. Создайте еще один файл класса, сохраните его как « Log.as » в папке проекта и добавьте этот код:

    1
    2
    3
    4
    5
    6
    7
    package {
        public class Log implements IDebugger {
            public function out(message:*):void {
                // Fill this in later
            }
        }
    }

Здесь нет ничего нового, но вот быстрое сокращение. Класс Main создает две кнопки и подключает их к двум прослушивателям событий, которые мы будем заполнять в следующем. Интерфейс IDebugger определяет один метод: out . Идея состоит в том, что это будет принимать сообщение в качестве единственного параметра и каким-то образом отображать его. Наконец, у нас есть две реализации интерфейса, Trace и Log , обе из которых просто заглушены прямо сейчас.

Давайте разработаем эти реализации прямо сейчас. В Trace.as удалите комментарий в методе out и замените его следующей логикой:

1
2
3
public function out(message:*):void {
    trace(message);
}

Не похоже, что он делает много, просто оборачиваясь вокруг встроенной функции trace . И это все. Но давайте завершим другую реализацию.Откройте Log.as и уточните его немного:

1
2
3
4
5
6
7
8
package {
    import flash.external.*;
    public class Log implements IDebugger {
        public function out(message:*):void {
            ExternalInterface.call("console.log", message.toString());
        }
    }
}

Теперь все становится интересным. После импорта externalпакета мы можем использовать ExternalInterfaceдля вызова функции JavaScript console.log. Вместо использования traceмы можем использовать консоль JavaScript, встроенную в большинство современных браузеров (обратите внимание, что я не удосужился проверить доступность ExternalInterfaceили console.log; я собираюсь осветить проверку ошибок для более удобных иллюстраций).

Теперь вернемся к Main.as , где мы можем использовать эти классы. Сначала добавьте _debuggerсвойство в класс, напечатанное как IDebugger.

1
2
3
public class Main extends Sprite {
    private var _debugger:IDebugger;
    // Class continues...

А в конструкторе мы добавим некоторую логику, чтобы определить, где работает Flash Player, и на основе этой информации создать экземпляр одного конкретного класса или другого.

1
2
3
4
5
6
7
public function Main() {
    if (Capabilities.playerType == "ActiveX" || Capabilities.playerType == "PlugIn") {
        _debugger = new Log();
    } else {
        _debugger = new Trace();
    }
    // Constructor continues...

Эта логика использует Capabilitiesкласс, чтобы получить тип игрока, который будет одним из нескольких Stringзначений. Если это значение равно «ActiveX» или «Подключаемый модуль», то проигрыватель Flash Player работает как подключаемый модуль браузера (в частности, в Internet Explore, если указано значение «ActiveX»), или любой другой браузер, если используется значение «Подключаемый модуль». «). Итак, если мы в браузере, мы создаем _debuggerкак Logобъект, чтобы использовать консоль JavaScript. В противном случае, мы, вероятно, работаем в Flash IDE (хотя AIR или проектор все еще доступны), поэтому мы можем использовать Traceобъект.

Наконец, давайте заполним два метода-обработчика событий, message1и message2.

1
2
3
4
5
6
private function message1(e:MouseEvent):void {
    _debugger.out("Hello.")
}
private function message2(e:MouseEvent):void {
    _debugger.out("Where is the bathroom?")
}

Обычно вы просто используете, traceчтобы получить текст для отображения, но вместо этого мы используем _debuggerобъект. В зависимости от того, как он был настроен изначально, вы получите трассировку на панель «Вывод» или запись в консоль JavaScript.

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

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

Чтобы проверить это, вам нужно запустить SWF один раз в IDE и один раз в браузере. Начните с запуска фильма во Flash и нажатия кнопок — вы увидите следы.

Сообщения на панели «Вывод»

Чтобы проверить вторую часть, вам понадобится SWF, работающий в браузере. Это, к сожалению, не так просто. Вам понадобится HTML-страница для размещения SWF (создайте свой собственный или используйте шаблоны публикации Flash), иначе ExternalInterfaceне будет работать. Затем вам нужно будет либо поместить файлы HTML и SWF на сервер и перейти на страницу, чтобы она обслуживалась по протоколу HTTP (локальный хост должен подойти), либо вам нужно будет настроить параметры безопасности Flash Player, чтобы локальный SWF-доступ к HTML-странице.

Как бы то ни было, откройте консоль (находится в Firebug, если вы используете Firefox, или веб-инспектор в Safari, Chrome и других браузерах WebKit или в Developer Tool в последней версии Internet Explorer), загрузите страницу HTML, и начать нажимать на кнопки.

Сообщения в JavaScript-консоли Safari

(И да, мне известно о возможности проигрывателя Flash Debug отправлять traceсообщения в файл журнала для просмотра трассировок в браузере. Этот пример, ну, в общем, пример. Он полезен, но, вероятно, не так полезен, как проигрыватель отладки хитрость — перейдите сюда для получения дополнительной информации — хотя вы можете утверждать, что если разработчику, не являющемуся Flash, необходимо увидеть некоторые из ваших следов, может быть проще использовать консоль, чем устанавливать и настраивать проигрыватель отладки)

Теперь представьте, что вы можете создавать дальнейшие реализации. Возможно, одна реализация создает TextFieldобъект на сцене и просто выплевывает сообщения прямо в SWF. Более продвинутый может использовать LocalConnectionобъект для подключения к приложению AIR или к серверу сокетов для подключения к любому количеству других программ (даже к вашему телефону). Вы могли бы даже развить идею JavaScript и отображать сообщения в браузере, но за пределами консоли.

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


Мы попытаемся сделать полиморфизм еще более практичным, работая над проектом, на котором сосредоточено внимание в следующем уроке, но мы приближаемся к концу этого урока. Я хотел бы упомянуть несколько дополнительных возможностей интерфейсов, которые я еще не имел возможности использовать. Я просто упомяну эти аспекты и оставлю их как уведомления о том, что эти возможности существуют, не вдаваясь в объяснения. Однако я включил рабочие (если простые) примеры этих методов в пакет загрузки, все они содержатся в папке «advanced-interface-examples».

Во-первых, я хочу упомянуть возможность реализации нескольких интерфейсов в классе. Вы можете extendтолько один другой класс, но вы можете implementстолько, сколько хотите или нужно. Для этого вы просто пишете, implementsа затем перечисляете интерфейсы, которые хотите использовать, через запятую. Например:

1
2
3
4
5
package {
    public class MultiImplementation implements IOne, ITwo, IThree {
        //…
    }
}

Это вводит вас в три контракта на внедрение, которые вы затем должны выполнить.

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

1
2
3
4
5
package {
    public interface ISuper {
        function basicMethod():void;
    }
}

Мы можем создать другой интерфейс, который расширяется ISuperтак:

1
2
3
4
5
package {
    public interface ISub extends ISuper {
        function fancyMethod():void;
    }
}

Расширение интерфейса означает, что подынтерфейс наследует объявление метода суперинтерфейса и может добавить больше объявлений к миксу. Таким образом, в приведенном выше примере, ISubможет определять только один метод в файле, но на самом деле требуется два метода для реализации: basicMethodи fancyMethod. Например:

01
02
03
04
05
06
07
08
09
10
package {
    public class SubImplementation implements ISub {
        public funciton basicMethod():void {
            trace("basic method");
        }
        public funciton fancyMethod():void {
            trace("fancy method");
        }
    }
}

Это будет успешной реализацией. Уходить не basicMethodстал бы.

Наконец, я хочу кратко упомянуть, что вы, безусловно, можете создать класс, который является одновременно подклассом и реализацией. Вам просто нужно сначала перечислить суперкласс, а затем интерфейс (ы). Например:

1
2
3
4
5
6
package {
    import flash.display.Sprite;
    public class FancySprite extends Sprite implements IFancy {
        // …
    }
}

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

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


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

Давайте сначала создадим проблему. Мы будем работать с двумя файлами Flash, каждый со своим собственным классом документов. Флэш-файл » Main.fla » загрузится во флеш-файл » Loadee.flaНаш первоначальный взгляд на это выглядит следующим образом:

  1. Создайте папку проекта для всех файлов, которые вы будете создавать.
  2. Создайте два файла Flash, один с именем Main.fla, а другой с именем Loadee.fla .
  3. Создайте классы документов для них обоих, называемые Main.as и Loadee.as .
  4. Файлы Flash могут оставаться пустыми, но обязательно установите класс документа для каждого из них.
  5. Напишите некоторый код для классов документов.

Класс Loadee.as будет выглядеть следующим образом (обратите внимание, что целью этого фрагмента Flash в этом упражнении является предоставление кода в килобайтах. Ниже приводится выдержка из списка. Чтобы получить весь _pixelsмассив, просмотрите пакет загрузки для папка для этого класса называется crossstalk-start :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package {
 
    import flash.display.*;
    import com.greensock.*;
    import com.greensock.easing.*;
 
    public class Loadee extends Sprite {
 
        private var _bmd:BitmapData;
        private var _bmp:Bitmap;
 
        public function Loadee() {
            _bmd = new BitmapData(_pixels[0].length, _pixels.length, false, 0xFF0000);
            _bmp = new Bitmap(_bmd);
            addChild(_bmp);
 
            var iLen:uint = _pixels.length;
            for (var i:uint = 0; i < iLen; i++) {
                var jLen:uint = _pixels[i].length;
                for (var j:uint = 0; j < jLen; j++) {
                    _bmd.setPixel(j, i, _pixels[i][j]);
                }
            }
 
            _bmp.alpha = 0;
            display();
        }
 
        public function display():void {
            TweenLite.to(_bmp, 0.5, {alpha:1});
        }
 
        private var _pixels:Array = [
            [0xc95016, 0xcc5316, 0xcc5716, 0xcf5716, 0xcf5716, 0xcf5716, 0xcf5a19,
            0xcf5a19, 0xcf5a19, 0xcf5a19, 0xcf5d19, 0xcf5d19, 0xcf5d19, 0xcf6019,
            0xcf6019, 0xcf6019, 0xd26019, 0xd2601d, 0xd2641d, 0xd2641d, 0xd2641d,
            0xd2671d, 0xd2671d, 0xd2671d, 0xd2671d, 0xd26a1d, 0xd26a1d, 0xd26a1d,
            0xd26d1d, 0xd66d20, 0xd66d20, 0xd66d20, 0xd67020, 0xd67020, 0xd67020,
            0xd67020, 0xd67020, 0xd67020, 0xd67020, 0xd67020, 0xd67020, 0xd67020,
 
            // …
            // To avoid an impossibly long scrolling page, this code is truncated.
            // Please look at the class Loadee.as in the folder "crosstalk-start"
            // for the complete code.
 
            ]
        ]
 
    }
}

Я не буду вдаваться в логику этого класса, достаточно сказать, что он программно рисует растровое изображение, используя значения пикселей, хранящиеся в двумерном массиве. Другими словами, это растровое изображение, хранящееся в текстовом файле. Когда вы закончите, вы увидите логотип Activetuts +. На данный момент логотип имеет альфа 0, поэтому вы его не увидите. Тем не display()менее, существует метод, который использует TweenLiteдля постепенного исчезновения логотипа.

Main.as файл класс сосредоточен вокруг загрузки SWF загружаемых доменов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package {
 
    import flash.display.*;
    import flash.events.*;
    import flash.net.*;
 
    public class Main extends Sprite {
 
        public function Main() {
            var loader:Loader = new Loader();
            loader.load(new URLRequest("Loadee.swf"));
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
            addChild(loader);
        }
 
        private function onLoadComplete(e:Event):void {
            var loadedSwf:Sprite = e.target.content as Sprite;
 
        }
 
    }
 
}
  1. Опубликовать фильм «Loadee»
  2. Запустите (Test Movie) фильм «Main».

Вы не увидите ничего, кроме чистого холста. Причина в том, что мы не вызываем displayметод в Loadeeобъекте. Теперь вот где все становится сложнее.

Предположим, мы хотим позвонить displayиз MainSWF. Причины могут быть любой из ряда веских причин. Вполне возможно, что мы хотим загрузить SWF-файл, но, возможно, не сразу его отображать, поэтому мы хотим контролировать, когда наступит этот момент отображения. Однако мы хотим, чтобы LoadeeSWF контролировал собственную логику анимации дисплея.

Итак, если вы напишите этот код:

1
2
3
4
private function onLoadComplete(e:Event):void {
    var loadedSwf:Sprite = e.target.content as Sprite;
    loadedSwf.display();
}

Вы получите ошибку компилятора, потому что displayметод не определен Sprite, как и loadedSwfтип переменной.

1
decoupled-swfs-start/Main.as, Line 18 1061: Call to a possibly undefined method display through a reference with static type flash.display:Sprite.

Итак, вы полагаете, что можете просто ввести loadedSwfпеременную (и привести e.target.content) в качестве Loadeeобъекта.

1
2
3
4
private function onLoadComplete(e:Event):void {
    var loadedSwf:Loadee = e.target.content as Loadee;
    loadedSwf.display();
}

И когда вы протестируете Mainфильм, вы увидите, что он работает:

SWF-файлы, запущенные в IDE

Это здорово, но перед тем, как закрыть Mainфильм, нажмите Command-B (Control-B на ПК), чтобы открыть Bandwidth Profiler (также доступен в разделе « Просмотр»> «Bandwidth Profiler» во время запуска тестового фильма). Обратите внимание на размер поля. Мой говорит 13989 байт .

Профилировщик пропускной способности, показывающий размер SWF

Теперь отмените последние изменения. Мы восстановим тип as Spriteи удалим строку, которая вызывает display():

1
2
3
private function onLoadComplete(e:Event):void {
    var loadedSwf:Sprite = e.target.content as Sprite;
}

И снова протестируйте фильм. Bandwidth Profiler должен все еще быть открыт с прошлого раза, но если нет, откройте его снова и отметьте размер. Моя теперь говорит 1016 байт .

Что произошло? Мы добавили более 13 000 байт… или около 13 КБ. Как?

Как только мы ввели loadedSwfпеременную как a Loadee, мы используем Loadeeкласс в нашем Mainклассе. Обычно такого рода вещи не так уж важны, так как вам нужно объединить много разных классов в один финальный SWF. Но в этот момент мы отменили цель загрузки Loadee по отдельности, так как почти весь вес этого SWF также содержится в Loadeeклассе TweenLite. Благодаря использованию Loadeeкласса, даже в качестве простого типа данных для одной переменной (как приведенной), компилятор Flash затем компилирует весь класс в SWF. И любые другие классы, используемые этим классом.

На этом этапе два наших SWF-файла «связаны» или, как я это называю, между двумя SWF-файлами существует «перекрестная помеха». Классы, которые должны принадлежать только одному SWF-файлу, компилируются (возможно, случайно) в другой.

Интерфейсы могут решить эту проблему. Мы можем сохранить правильный тип данных, но отключить два SWF-файла. Вот как.

Создайте новый файл интерфейса в папке проекта, можете назвать его ILoadee.as . Добавьте этот код:

1
2
3
4
5
package {
    public interface ILoadee {
        function display():void;
    }
}

Далее мы хотим, чтобы наш Loadeeкласс реализовал этот интерфейс. Добавьте это к объявлению класса:

1
public class Loadee extends Sprite implements ILoadee {

Теперь обязательно опубликуйте LoadeeSWF. Мы внесли изменения в него, поэтому его необходимо переиздать.

Наконец, Main.asпеределайте то, что мы вынули совсем недавно. Мы позвоним displayна loadedSwf, но вместо того , чтобы использовать в Loadeeкачестве типа данных, мы будем использовать :I Loadee

1
2
3
4
private function onLoadComplete(e:Event):void {
    var loadedSwf:ILoadee = e.target.content as ILoadee;
    loadedSwf.display();
}

Если вы проверите Mainсейчас, вы обнаружите, что это работает.

SWF-файлы работают снова, с небольшим размером файла, как указано в Bandwidth Profiler

И еще раз проверьте Bandwidth Profiler, и вы увидите что-то более похожее на оригинальный размер Main. У меня 1146 байт. Так что он немного увеличился (130 байт), что связано с использованием интерфейса. Но это ничто по сравнению с увеличением на 13 КБ, когда мы использовали класс напрямую. И в то же время мы сохранили безопасность типов, вводя данные в наши loadedSwf. Если мы попытаемся использовать его неправильно, скажем, позвонив showвместо displayили попытаться передать аргументы display, то компилятор выдаст ошибки и поможет нам выйти из затруднительного положения.

Если вы хотите проверить, какие классы на самом деле компилируются в различных сценариях, вы можете обратиться к моему Краткому совету по операторам импорта, чтобы узнать, как увидеть, какие классы скомпилированы в SWF.

Если вы планируете использовать эту технику (и я надеюсь, что вы это сделаете), вы также должны знать о крошечной ошибки. Позвольте мне проиллюстрировать это: вернитесь Loadee.asи просто удалите implements ILoadeeчасть (то есть мы больше не реализуем ILoadeeинтерфейс, просто расширяем его Sprite).

1
public class Loadee extends Sprite {

Переиздание LoadeeSWF.

Теперь повторно протестируйте MainSWF. Вы увидите всплывающую ошибку, но не ошибку компилятора. Это ошибка во время выполнения, которая будет отображаться на панели «Вывод» при запуске в IDE Flash.

1
2
TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at Main/onLoadComplete()

Проблема заключается в том, что когда мы cast e.target.content as ILoadee, а на самом деле загружаемый контент неILoadee типа, то мы получим nullобратно. Таким образом, loadedSwfесть null, и поэтому, когда мы вызываем loadedSwf.display()следующую строку, это приводит к ошибке: вы не можете вызвать метод для чего-то не существует.

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

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

1
2
3
4
5
6
7
private function onLoadComplete(e:Event):void {
    var loadedSwf:ILoadee = e.target.content as ILoadee;
    if (!loadedSwf) {
        throw new Error("The loaded SWF does not implement the ILoadee interface.")
    }
    loadedSwf.display();
}

Он проверяет состояние loadedSwf, не являющееся значением , и, если это так, останавливает оставшуюся часть метода, выдавая ошибку. Теперь мы по-прежнему получаем ошибку времени выполнения, как и раньше, но в этом случае мы получаем гораздо более полезную ошибку. Попробуйте еще раз протестировать MainSWF, и вы получите те же результаты, только более полезную формулировку в сообщении об ошибке.

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

1
2
3
4
5
6
7
8
private function onLoadComplete(e:Event):void {
    var loadedSwf:Sprite = e.target.content as Sprite;
    if (loadedSwf is ILoadee) {
        (loadedSwf as ILoadee).display();
    } else {
        trace("Need to do something with the loadedSwf variable as a Sprite.");
    }
}

Дело в том, что вы проверяете условие, что загруженный SWF-файл реализует интерфейс, и если да, то используете его, а если нет, делаете что-то еще. isТест ключевого слова вещь налево , чтобы увидеть , если это, ну, это вещь справа. То есть если loadedSwfофициально реализует ILoadeeинтерфейс, то получаем true. Если нет, мы получаем false.

Так что , если это являетсяILoadee объект, то мы будем рассматривать его в качестве объекта. Приведение по-прежнему необходимо, так как переменная официально a Sprite. Но мы можем быть уверены, что приведение будет успешным, потому что мы только что проверили переменную для этого типа.

Преимущество этого метода — гибкость. Вы можете включить более одного интерфейса. Может быть , некоторые ФНБ ILoadeeСФБ, и другие ILoadeeWithCallbackСФБ, и некоторые из них простые Sprites. Имея несколько ветвей в вашей логике, вы можете обращаться с каждой из них соответствующим образом.

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

  1. Создайте интерфейс (ы) для загрузки SWF (ов) (лучше, если это будет единый интерфейс для всех SWF… полиморфизм!)
  2. Реализуйте интерфейс в классе документа SWF, который будет загружен.
  3. В SWF, выполняющем загрузку, используйте интерфейс в качестве типа данных для загруженного содержимого.
  4. Чтобы быть в безопасности, проверьте успешную реализацию загруженного контента.

Вы можете найти пример готового проекта в папке «crosstalk-finish» в пакете загрузки.

Для протокола, я никогда не слышал, чтобы кто-нибудь еще называл это явление «перекрестными помехами», но это довольно подходящее слово для описания происходящего. Я просто хотел, чтобы вы это знали, потому что я не хочу, чтобы вы рисовали пустые взгляды, когда вы идентифицируете эту конкретную проблему, а затем говорите что-то вроде «Нам нужно устранить перекрестные помехи» другому разработчику.


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

Спасибо за то, что читаете и общаетесь со мной!