в пикселах. Первая проблема, вы
myButton._x=20, myButton._y=20;
Координаты в пикселах. Первая проблема, вы будете смеяться, что ежели позднее понадобится использовать эту кнопку на карте, координаты которой исчисляются широтой и долготой? (Смех за кадром.) Хорошо, а как насчёт игрушки с мозаичной структурой, где всё, вместо пикселов, измеряется отдельными частями (tile[x,y])? О, да, конечно, игра - это серьёзная проблема... Возможно, вы всё ещё в сомнениях, вероятно делали когда-то нечто подобное без проблем. В таком случае ситуация хуже, чем предполагалось. Ладно, а как вам такое: что будет, если, по непонятным причинам, понадобится поместить кнопку за пределами экрана? Разумеется, на черта вам это делать, а как насчёт того чувака, что сидит за вашим рабочим столом по понедельникам и вечно с бодуна? Не следует доверять лицам, использующим ваши классы, они всегда делают это не так, как нужно. Все они, как один, подлецы, извращенцы, злобные ублюдки и байстрюки. Сказать что-либо в их защиту крайне трудно, но объективности ради признаем, что напакостить они могут и случайно, к примеру, во вторник, после кофе, когда будут просто перемещать окошки... Так почему бы не обезопасить себя? Сделать это легко: заведите метод для изменения x, вместо проверок x на корректность всякий раз, как вы его меняете и заставьте его правильно работать.
Есть ещё одна причина для корректного измерения: вполне возможно у нашей кнопки окажутся друзья-товарищи, которым небезразлично её положение. Например, тень. Этот объект может быть отделён от кнопки (при соприкосновениях одна кнопка не должна отбрасывать тень на другую, так что приходится использовать раздельные уровни). Если установить x методом, вместо того, чтобы его переназначать, "тень" будет сразу же узнавать об изменениях. Существует ещё множество других ситуаций, когда требуется объединить несколько клипов в один "объект", а посему, при задании таких вещей, как позиция объекта, лучше хранить опции открытыми.
Итак, вышеописанная процедура избавляет от всех тех мерзких вещей, которые происходят, если устанавливать значения напрямую. Это, так сказать "кнут". А вот и "пряник". Как, к примеру, сделать значение доступным только для чтения? Ну, обычно мы отмечаем про себя, что вот данное значение устанавливать нельзя, или, когда это особенно важно, пишем прямо на боку монитора чёрным маркером. Между тем, если использовать методы getter/setter, жизнь облегчается, просто не создавайте метода, задающего значение (или создайте метод, сообщающий об ошибке, тут на выбор, кому как нравится).
Ещё одна приятная особенность состоит в том, что отныне вы не обязаны устанавливать непосредственные отношения между методами и данными. Вместо того чтобы говорить setX и setY, можно сказать проще: setLocation. Вместо того чтобы говорить setWidth и setHeight, мы можем сказать setSize. Разумеется, где-то внутри нашего класса существуют X, Y, Widht и Height, но пользователь не должен забивать себе этим голову. Или сделать иначе, для установки одних и тех же значений создать не один метод, а несколько. К примеру, метод setRectangle, который установит все 4 поля "одним залпом". Внутри он может вызвать оба метода setLocation и setSize, или те же setX, setY, setWidth, setHeight один за другим - ну вот, пользователи класса уже не теряются в догадках о происходящем (что является их обычным состоянием).
Неплохие аргументы за использование того, что зовётся "getters и setters"? Суть в том, что теперь класс обладает свойством, которым можно управлять только через методы класса. Исторически такие методы назывались getXX() и setXX(), отсюда и названия терминов: "getters и setters". Однако учтите, их следует использовать лишь в том случае, если вы не уверены, что данное свойство никогда не будет подтверждаться, ограничиваться, регламентироваться или обрабатываться как-то иначе, но точно знаете, что значение свойства никогда не будет преобразовано во что-то другое. Если же одолевает лень, попробуйте заставить себя сделать это несколько раз, и в будущем подобная практика станет естественной. Она может даже перерасти в привычку: поставьте себе цель делать это, после того как закомментируете код. Хм...
Погодите-ка, разве вышеупомянутый .NET-пример не использует "getters и setters"?
button1.Location = new Point (168, 168);
Это определенно не выглядит как "setter", не так ли? Ну, по правде говоря, это и не "setter", а то, что известно под именем "свойство" (Мд-а... ещё больше запутались!) в C#, и что автоматически вызывает метод get, когда значение читается, и метод set, когда значение устанавливается. "Майкрософту" так нравятся эти методы, что они встроили их в язык C# (на который обладают монополией), одним махом избавившись от необходимости когда-либо использовать методы getXX или setXX. Что ж, мы не имеем такой роскоши в ActionScript, поэтому придётся обзывать методы setXX и getXX вручную. Да, не так удобно, зато у них довольно посредственная поддержка векторной графики, вот!
Ладно, теперь, зная всё это, давайте попробуем заново создать класс Shape. На этот раз чуть меньше "псевдо" и чуть больше "кода".
// Shape Class Shape = function() { // тут ничего делать не надо } // Properties x,y,width,height // давайте использовать настоящие имена, раз уж на то пошло. // Methods setLocation( x, y ) { this.x = x; this.y = y; } getLocation() { return this.x; return this.y; // назревает проблемка? Хе-хе... } setSize( w, h ) { this.width = w; this.height = h; } getSize() { return this.width; return this.height; // что же делать? }
Не так уж много кода понадобилось написать, чтобы всплыла новая проблема: разумеется, с getLocation и getSize, ибо невозможно сделать возврат из метода дважды, а нам-то нужно вернуть два значения. Самое очевидное решение - запаковать оба значения в объект и вернуть объект, например: obj.x и obj.y. Это выход из положения, но как теперь сообщить пользователю, что его ожидает? Можно пойти на дополнительный шаг: вместо того, чтобы возвращать абстрактный объект, создадим стандартный объект, который будет использоваться всегда. Пусть это заставит кое-кого ответственнее относиться к работе, что ж, ответственность крайне важная черта, как для разработчиков, так и для продавцов софта.
Какова же основная единица измерения позиции? А это смотря какими параметрами измерять: x и y, шириной и долготой, xyz, или даже углом-радиусом, все они задают точку. Мы будем работать с 2D и использовать декартовы координаты, поэтому наша точка будет содержать x и y. Как вы уже наверно догадались, мы зададим точку как класс:
// Point Class Point= function( x, y ) { this.x = x; this.y = y; }
Теперь, чтобы задать позицию, можно сказать:
s1.setLocation( new Point(20, 30) );
Помимо решения проблемы с get, мы получили ещё ряд преимуществ. Во-первых, наш код стал гораздо чище. Достаточно только взглянуть на одну строку, чтобы понять, что позиция s1 устанавливается в определённую точку. Кроме того, метод setLocation теперь точно знает, что он получит, объект Point не такой уж и большой, но если нужно использовать более сложные объекты, очень пригодится знание того, что все значения установятся в дефолтные, если не будут заданы явно. Да и копирование свойств одного объекта в другой теперь становится проще:
s2.setLocation( s1.getLocation );
Конечно, это можно было бы сделать двумя командами, но что за нужда вникать в детали? Представьте, что копируете Rectangle, а не Location, вот вам уже и четыре команды. То ли дело, используя методы get и set State, можно копировать полное состояние чего-нибудь, например целого мувиклипа (x, y, rotation, alpha, ...)
Как видите, несколько выгодных позиций у нас уже есть, но избавляет ли это нас от необходимости использовать setX и setY? Если Location хранится в объекте Point, как тогда прямоугольник сохраняется в объекте Rectangle, здесь что, две версии? Суть в том, что всё по-прежнему сводится к x, y, ширине и высоте. Когда мы устанавливаем позицию, то вытаскиваем x и y из объекта Point и присваиваем их значения свойствам x и y. А ширина и высота, поступившие из объекта Size, присваиваются свойствам width и height. Объект Rectangle, устанавливающий все четыре свойства, может либо проделать это через объекты Location и Size, либо установить все свойства сам. И, конечно, всё ещё можно использовать методы setX и setY. Работает это примерно так:
// Методы setX( x ) { this.x = x; } getX( ) { return this.x; } setLocation( newLocation ) { this.x = newLocation.x; this.y = newLocation.y; } getLocation() { return new Point( this.x, this.y ); } setRectangle( newRectangle ) { this.x = newRectangle.x; this.y = newRectangle.y; this.width = newRectangle.width; this.height = newRectangle.height; } getRectangle() { return new Rectangle( this.x, this.y, this.width, this.height ); }
Несмотря на то, что этот код достаточно условный, он абсолютно реален. Или, возможно, реален "c некоторыми если".
<fla>
< Тут, наверное, должен был прилагаться какой-то исходник, но автор видимо постеснялся... ;)
(прим. переводчика) >
Отлично, считайте это первыми шагами. Может показаться, что, мол, так много кода и так мало делает, но красоту ООП (которую умом не понять, аршином общим не измерить...) вы, возможно, оцените, лишь написав программу целиком. Код будет находиться в родительских классах и будет работать, как только вы его вызовете. И уж совсем замечательно то, что написать этот код можно один только раз и на всю жизнь. Ха! Ну ладно, ладно! По отношению к вселенскому обману я лишь слегка преувеличил, и всё-таки: тот же класс можно запросто использовать в другом проекте - всуньте его в этот проект, да и дело с концом! Впрочем, наверняка сразу придётся его модифицировать. Не будем ради красоты концепции скрывать правду...
А теперь о подклассах.
<<
ООП во Flash 5 ( II ) >>