Дострочное пересдача экзамена
|
Создание и модификация объектов
Свойства уровня экземпляра
Как упоминалось в предыдущем разделе, при попытке обращения к значению свойства – например, присвоении myVariable значения person1.legs (myVariable = person1.legs;) – ActionScript всегда сначала ищет указанное свойство на уровне экземпляра, и только после это переходит к проверке объекта-прототипа. Свойство уровня экземпляра – это свойство, полученное экземпляром из описания класса при создании, либо свойство, значение которого присваивается путем непосредственной адресации к экземпляру. Экземпляр person1, созданный нами в предыдущем упражнении, имеет следующие свойства:
name = "Justin" // уровень экземпляра age = 16 // уровень экземпляра legs = 2 // уровень прототипа head = new Object() // уровень прототипа head.eyes = 2 // уровень прототипа head.memories = new Array() // уровень прототипа
Почему важно различать свойства уровней экземпляра и прототипа? В ситуации, когда свойства уровня экземпляра и уровня прототипа имеют одно и то же имя, значение свойства уровня экземпляра перекрывает значение уровня прототипа (хотя это относится только к данному экземпляру).
Допустим, экземпляр person1 получает свойство legs от объекта Person.prototype, поскольку при создании экземпляра это свойство не было присвоено. Однако если мы присвоим значение этому свойству, обратившись непосредственно к экземпляру – например, person1.legs = 1 ; – мы создадим у person1 свойство legs уровня экземпляра, и его значение перекроет значение, установленное прототипом. У всех прочих экземпляров останется две ноги – за исключением person1, который теперь имеет только одну. Значение уровня прототипа попрежнему существует и для этого экземпляра, только теперь оно невидимо, перекрыто свойством уровня экземпляра с таким же именем.
Мы можем удалить свойство legs уровня экземпляра – вот таким образом:
delete person1.legs;
В результате person1 вновь станет отражать свойство legs (и его значение), установленное объектом-прототипом класса Person.
Еще один важный аспект, касающийся свойств уровня экземпляра – то, что благодаря им экземпляры могут иметь свои уникальные свойства вдобавок к полученным из описания класса и унаследованным от прототипа. Чтобы добавить такое свойство, нужно просто обратиться непосредственно к экземпляру. Например:
person2.favoriteFood = "Pizza";
Теперь person2 станет единственным экземпляром, имеющим свойство favoriteFood. Конечно, мы можем запросто добавить такое свойство и прочим экземплярам (присвоив его напрямую либо добавив в объект-прототип), но пока мы этого не сделаем, свойство favoriteFood будет только у экземпляра person2.
Примечание Если добавить свойство в описание класса, то это свойство будут получать лишь вновь создаваемые экземпляры, начиная с этого момента, но не те, что уже существуют. Причина в том, что конструктор функции (описание класса) присоединяет свойства к экземплярам лишь в процессе их создания.
Свойства уровня экземпляра позволяют индивидуализировать экземпляры, не принося в жертву преимущества класса объектов.
Создание подклассов
Наш класс Person приобретает все большую функциональность: мы можем создавать его экземпляры, указывая для них имена и возраст, и еще они могут наследовать свойства объекта-прототипа. Но, как в реальной жизни, могут существовать еще и классы внутри нашего класса Person: доктора, юристы, Flash-разработчики и так далее – разные подклассы людей. Эти подклассы разделяют признаки класса Person (имя, возраст, ноги, голова), но имеют и свои, уникальные. Если, например, вы создаете класс Доктор, он должен обладать такими особыми характеристиками, как специальность, звание (" Dr."), и так далее, и в то же время – всеми признаками класса Person.
- Откройте файл class2.fla из папки Lesson06/Completed. Откройте панель Действия, выделите кадр 1. Удалите два действия trace в конце скрипта, затем добавьте следующее:
_global.Doctor = function(almaMater){ this.almaMater = almaMater; } Doctor.prototype.title = "Dr.";
Здесь мы создаем конструктор функции, описывающий новый класс объектов – класс Doctor. Каждый новый экземпляр этого класса объектов получит свойство almaMater, значение которого передается конструктору функции при создании экземпляра. Также мы придали классу Doctor прототип со свойством title, имеющим значение "Dr.". Поскольку это звание носят все доктора, мы можем поместить его в объект-прототип класса Doctor.
Давайте создадим экземпляр нашего нового класса.
- Поместите следующие строки в конец скрипта:
frankenstein = new Doctor("Transylvania"); trace(frankenstein.almaMater); trace(frankenstein.title); trace(frankenstein.legs);
Первое действие создает новый экземпляр класса Doctor с именем frankenstein. Последующие действия trace покажут нам результат.
- Командой Управление > Проверить фильм (Control > Test Movie) запустите тест проекта.
Откроется окно Выход (Output), и в нем отобразится следующее:
Transylvania Dr. undefined
Как видите, со свойством almaMater все в порядке (это свойство уровня экземпляра, и оно было добавлено при создании экземпляра), и со свойством title тоже (благодаря объекту Doctor.prototype ), а вот свойство legs не существует.
Нашей целью было сделать класс Doctor подклассом класса Person, чтобы все экземпляры класса Doctor наследовали такие свойства, как legs, head и так далее. Только это не произойдет само собою – где-либо в нашем скрипте мы должны сообщить ActionScript о том, что Doctor есть подкласс класса Person.
- Закройте тестовый фильм и вернитесь в среду разработки. Выделив кадр 1, поместите следующую строку непосредственно перед Doctor.prototype.title = "Dr." :
Doctor.prototype = new Person();
Эта строка скрипта создает связь между классами Doctor и Person. Она указывает, что объект-прототип класса Doctor должен наследовать объекту-прототипу Person.
В результате все экземпляры класса Doctor будут отражать свойства не только Doctor.prototype, но также и Person.prototype. Обратное неверно; экземпляры класса Person будут отражать только свойства Person.prototype. Теперь Doctor – подкласс Person, а Person – надкласс (еще говорят суперкласс) класса Doctor.
Существует причина, по которой мы поместили нашу последнюю строку перед строкой устанавливающей свойство title объекта Doctor.prototype. При выполнении добавленной нами строки объект Doctor.prototype, по существу, полностью очищается, все свойства, которые в нем могли быть, заменяются свойствами объекта Person.prototype. Если бы мы добавили в объект Doctor.prototype свойство title до этого, оно было бы утрачено. Вместо этого в нашем скрипте мы добавляем свойство title после того, как Doctor.prototype наследует объекту Person, и проблема решена.
В конце нашего скрипта остались три действия trace, которые прежде (на шаге 3) вернули такие результаты:
trace(frankenstein.almaMater);// вернуло "Transylvania" trace(frankenstein.title); );// вернуло "Dr." trace(frankenstein.legs);// вернуло undefined
Давайте проверим результаты теперь.
- Командой Управление > Проверить фильм (Control > Test Movie) запустите тест проекта.
Откроется окно Выход (Output), и в нем отобразится следующее:
Transylvania Dr. 2
Как видите, frankenstein имеет теперь две ноги (тогда как прежде это свойство было не определено – undefined ). Это результат того, что экземпляры Doctor теперь наследуют, как мы выяснили на предыдущем шаге, свойства не только Doctor.prototype, но и Person.prototype. По той же причине frankenstein имеет теперь голову – head, а в голове – memories, и так далее. Если теперь мы изменим любое свойство Person.prototype, эти изменения отразятся не только на экземплярах класса Person, но также и на экземплярах класса Doctor, ведь он – подкласс класса Person. Дело, по существу, обстоит так. Когда Flash пытается обратиться к свойству legs экземпляра frankenstein (на уровне экземпляра), он такого свойства не находит. Тогда он заглядывает в объект Doctor.prototype (поскольку frankenstein есть экземпляр класса Doctor ). Такого свойства нет и там. Наконец Flash обращается к объекту Person.prototype (поскольку Doctor есть подкласс класса Person ) – и здесь находит-таки свойство legs.
Теперь вы, возможно, заинтересовались такими свойствами класса Person, как name и age. Эти свойства не входят в объект Person.prototype, так будут ли экземпляры класса Doctor наследовать их? Нет, не будут. Чтобы исправить это, нам нужно внести изменения в описание класса Doctor.
- Закройте тестовый фильм и вернитесь в среду разработки. Выделите кадр 1 и измените описание класса Doctor следующим образом:
_global.Doctor = function(name, age, almaMater){ this.base = new Person(name, age); this.name = this.base.name; this.age = this.base.age; delete this.base; this.almaMater = almaMater; }
Первое, что вы замечаете в обновленном описании класса Doctor – то, что появились два новых аргумента (вдобавок к almaMater, который был и прежде): name и age. Все три значения используются в последующих строках, обеспечивающих создание экземпляров класса Doctor.
В первой строке используются значения name и age, переданные конструктору функции, для создания нового экземпляра класса Person. Да-да, конструктор функции Doctor создает экземпляр класса Person. Как видите, этот экземпляр имеет имя и путь this.base. Видя такую строку, ActionScript создает экземпляр. При этом this.base получает свойства name и age (как все экземпляры класса Person ). Следующие две строки конструктора функции задают значения свойств name и age для будущих экземпляров класса Doctor ( this.name и this.age ), причем значения берутся у this.base (экземпляр класса Person, созданный в первой строке). Четвертая строка конструктора функции удаляет экземпляр this.base – он был нужен лишь временно. Пятая строка задает свойство almaMater для будущих экземпляров (это уже было на шаге 1).
Примечание Это показывает, сколь динамичным может быть при необходимости конструктор функции: функция может создать временный объект (как было показано), выполнить математические вычисления, другие действия, и много чего еще.
Вы, вероятно, думаете, мол, к чему такие сложности, разве нельзя было выполнить описание класса вот так:
_global.Doctor = function(name, age, almaMater){ this.name = name; this.age = age; this.almaMater = almaMater; }
Верно, так было бы проще, и в большинстве случаев подошло бы, но представьте себе, что в описании класса Person с переданными ему значениями name и age производятся какие-либо действия – скажем, все буквы name делаются заглавными, или что-то в этом роде? Тогда в последнем варианте скрипта экземпляры класса Doctor оказались бы лишены этого, тогда как в первоначальном такого не случится.
- Обновите строку frankenstein = new Doctor("Transylvania"); таким образом:
frankenstein = new Doctor("Frankenstein", 85, "Transylvania");
Необходимость такого изменения вызвана тем, что мы добавили в конструктор функции Doctor параметры age и name. В результате экземпляр frankenstein при создании получит свойства name со значением "Frankenstein", age со значением 85 и almaMater со значением "Transylvania".
- В конец скрипта добавьте следующие строки:
trace(frankenstein.name); trace(frankenstein.age);
Эти действия позволят нам увидеть результаты сделанных изменений. Всего в конце скрипта у нас получилось пять действий trace (три должны остаться с шага 2).
- Командой Управление > Проверить фильм (Control > Test Movie) запустите тест проекта.
Откроется окно Выход (Output), и в нем отобразится следующее:
Transylvania Dr. 2 Frankenstein 85
Экземпляр frankenstein полностью готов!
Теперь пришло время представить для рассмотрения свойство _proto_. Каждый объект-прототип класса и каждый экземпляр класса имеют свойство _proto_. Его значение показывает, где следует искать свойства, не обнаруженные у самого экземпляра. Как мы знаем, для экземпляра frankenstein в таком случае поиск будет продолжаться в объекте Doctor.prototype. Таким образом, frankenstein._proto_ имеет значение Doctor.prototype (экземпляр наследует этому объекту-прототипу). Поскольку класс Doctor наследует классу Person, Doctor.prototype._proto_ равно Person.prototype (один прототип класса наследует другому объекту-прототипу класса). Это свойство _proto_ создается и получает значение автоматически при создании экземпляра или подкласса, и его всегда можно изменить, если в том возникнет нужда. Предположим, мы создали класс Lawyer и хотим связать frankenstein с этим классом (вместо класса Doctor ). Тогда мы просто используем такую строку скрипта:
frankenstein._proto_ = Lawyer.prototype;
После этого frankenstein можно считать экземпляром класса Lawyer, наследующим характеристики прототипа этого класса. При этом его "докторские" характеристики (унаследованные от объекта Doctor.prototype ) исчезнут начисто!
Осталось рассказать еще о двух важных аспектах классов объектов: составе и наследовании. Их понимание поможет вам лучше осмыслить концепцию построения объектов. В принципе, смысл этих двух аспектов заключается в двух словах: иметь и быть. Если мы посмотрим на два класса объектов, созданных нами ( Person и Doctor ), то обнаружим следующее:
- Экземпляр Person имеет name: это состав.
- Экземпляр Person имеет head: это состав.
- Экземпляр Person имеет legs: это состав.
- Экземпляр Doctor есть Person: это наследование.
- Экземпляр Doctor имеет title: это состав.
Эта концепция имеет место и в реальном мире. Вот, например:
- Чизбургер есть сэндвич есть пища: это наследование.
- Автомобиль имеет кузов имеет дверь имеет окно: это состав.
Рассуждая таким образом, вам будет легко проектировать новые классы объектов. Когда что-либо является чем-то более общим (отношение есть), это хороший кандидат для создания подкласса. Там, где возникает отношение, появляется возможность создать свойство объекта.
Наследование также имеет некоторые свои аспекты, но это уже выходит за пределы тематики данной книги. Теперь, когда вы понимаете, как создаются любые классы объектов (и каково их сходство с объектами из реальной жизни), вам будет много проще постигнуть процесс разработки приложений. К концу следующего раздела мы обобщим все, что вы узнали нового и продемонстрируем, как это можно использовать в проектах.
- Закрыв тестовый фильм, вернитесь в среду разработки. Сохраните свою работу под именем class3.fla.
В следующем упражнении мы продолжим работу с этим файлом.