Наверно, как и большинство из вас познакомившись с объектно-ориентированным подходом в программировании, я стал видеть классы всюду и везде, причем порой, даже там, где в них и не было необходимости. Очень быстро мои приложения стали просто изобиловать классами, которые в большинстве своем не сильно отличались друг от друга (если вообще отличались). Сегодня я предлагаю поговорить о средстве, которое гарантированно поможет вам избавиться от «лишних» классов и в тоже время внесет стройности в ваши приложения. Безусловно, я уверен, что среди читателей Garbage Collector’а есть те, кто знаком с практикой описанной в этой статье (от них я жду комментариев), но также уверен, что кому-то этот материал окажется новым и полезным. Ну вот, со вступлением покончено, теперь в бой.
За окном осень вот-вот вступит в свои права, так что пример я предлагаю взять осенний — программа, имитирующая листопад. В задачи программы входят генерация листьев и последующее управление ими. Так как все листья обладают схожими качествами, мы определяем базовый класс Leaf, описав в нем вид дерева, которому лист принадлежит, размер и оттенок (методы и прочие свойства я показывать не буду):
-
package
-
{
-
public class Leaf
-
{
-
public var type:String;
-
public var size:String;
-
public var tint:String;
-
-
public function Leaf (type:String, size:String, tint:String)
-
{
-
this.type = type;
-
this.size = size;
-
this.tint = tint;
-
}
-
-
// прочие методы и свойства
-
}
-
}
Теперь мы знаем, что наш листопад происходит в лесу, где растут, например, клены и березы. А так же мы знаем, что кленовые листья большие и темные, а березовые – маленькие и светлые. То есть параметр type определяет остальные два параметра. Логичным на первый взгляд, кажется скрыть эту зависимость в подклассах Leaf: MapleLeaf и BirchLeaf:
-
package
-
{
-
public class MapleLeaf extends Leaf
-
{
-
public function MapleLeaf ()
-
{
-
super('maple', 'big', 'dark')
-
}
-
}
-
}
-
package
-
{
-
public class BirchLeaf extends Leaf
-
{
-
public function BirchLeaf ()
-
{
-
super('birch', 'small', 'light');
-
}
-
}
-
}
А теперь давайте посмотрим на наши новые классы внимательно. Отличаются ли они чем-то от базового класса Leaf? В целом — нет, с точки зрения API это абсолютно идентичные классы, если не считать создание экземпляров. К тому же с таким «классовым» подходом в случае добавления нового вида листьев нам придется вводить каждый раз новый подкласс Leaf. А если вы захотите добавить интерактивности в приложение в виде возможности пользователю самому создавать новые виды листьев с произвольными значениями своих свойств на этапе выполнения приложения, тогда мы просто не можем создать новый класс? Думаю, негибкость создания подклассов в приведенном примере очевидна.
И так нам нужен способ избавиться от лишних классов, получить удобный способ инстанцирования новых объектов, а также формирование новых «видов» объектов на этапе выполнения, ну и конечно возможность использовать наше решение повторно. Решить все эти задачи нам поможет паттерн проектирования Прототип (Prototype), суть которого заключена в создании новых экземпляров класса, обладающих необходимыми характеристиками, путем клонирования уже существующих экземпляров (прототипов) с заранее заданными характеристиками.
Есть несколько вариантов реализации это паттерна. Я предлагаю вашему вниманию вариант с менеджером прототипов (иногда используют термин — диспетчер, однако чтобы не вызывать неверных ассоциаций с EventDispatcher, я решил использовать именно такое название). Но обо всем по порядку.
Как я уже сказал, паттерн основывается на клонировании существующих экземпляров. Таким образом, у любого прототипа должен присутствовать метод clone() который должен возвращать точную копию экземпляра. Т. к. каждый клонируемый объект обладает свойственной его классу структурой, то создать базовый класс с реализованным в нем методом clone() не получится. Да и к тому же вполне возможно, что класс, который вы собираетесь наделить способностью клонироваться, уже от кого-то наследуется. Поэтому мы определяем интерфейс IPrototype (ЙаПрототип:)), который должны реализовывать все классы, экземпляры которых мы будем клонировать:
-
package
-
{
-
public interface IPrototype
-
{
-
function clone ():IPrototype;
-
}
-
}
Теперь модифицируем наш класс Leaf реализовав в нем метод clone():
-
package
-
{
-
public class Leaf implements IPrototype
-
{
-
public var type:String;
-
public var size:String;
-
public var tint:String;
-
-
public function Leaf (type:String, size:String, tint:String)
-
{
-
this.type = type;
-
this.size = size;
-
this.tint = tint;
-
}
-
-
public function clone ():IPrototype
-
{
-
return new Leaf(type, size, tint);
-
}
-
// прочие методы и свойства
-
}
-
}
Теперь мы можем создать «кленовый» экземпляр класса Leaf и в случае чего его клонировать:
-
var mapleLeaf:Leaf = new Leaf('maple', 'big', 'dark');
-
var anotherMapleLeaf:Leaf = mapleLeaf.clone() as Leaf;
Так как возвращаемый методом clone() тип объявлен как IPrototype, мы приводим к нужному типу с помощью оператора as.
С первой задачей мы справились, у нас всего на всего один класс. Теперь реализуем удобный способ создания новых экземпляров на базе прототипов, а также возможность создавать и удалять прототипы на этапе исполнения. Для этого создадим класс PrototypeManager, который должен хранить в себе прототипы, предоставлять возможность регистрации и удаления прототипов, и главное, создавать новые экземпляры. Чтобы получить доступ к тому или иному прототипу, каждый из них будет иметь уникальный строковый идентификатор. И так, давайте посмотрим, как может выглядеть реализация PrototypeManager:
-
package
-
{
-
public class PrototypeManager
-
{
-
private var __prototypes:Object;
-
-
public function PrototypeManager ()
-
{
-
__prototypes = {};
-
}
-
-
public function addPrototype (prototypeName:String, prototype:IPrototype, overridePrecursor:Boolean=false):Boolean
-
{
-
if(__prototypes[prototypeName] == undefined || overridePrecursor)
-
{
-
__prototypes[prototypeName] = prototype;
-
return true;
-
}
-
else
-
{
-
return false;
-
}
-
}
-
-
public function removePrototype (prototypeName:String):Boolean
-
{
-
if(__prototypes[prototypeName] != undefined)
-
{
-
delete __prototypes[prototypeName];
-
return true;
-
}
-
else
-
{
-
return false;
-
}
-
}
-
-
public function make (prototypeName:String):IPrototype
-
{
-
if(__prototypes[prototypeName] != undefined)
-
return (__prototypes[prototypeName] as IPrototype).clone();
-
else
-
return null;
-
}
-
}
-
}
Как же работает PrototypeManager? А работает он просто. PrototypeManager хранит все прототипы в своем пуле под уникальными строковыми идентификаторами, при необходимости используя метод прототипа clone() создает его копию. Так же PrototypeManager предоставляет методы добавления, перезаписи и удаления прототипов хранящихся в пуле. Теперь давайте посмотрим на пример работы с PrototypeManager:
-
// Создаем экземпляр PrototypeManager
-
var pm:PrototypeManager = new PrototypeManager();
-
-
// Добавляем прототипные экземпляры в пул менеджера под уникальными именами
-
pm.addPrototype('mapleLeaf', new Leaf('maple', 'big', 'dark'));
-
pm.addPrototype('birchLeaf', new Leaf('birch', 'small', 'light'));
-
-
// Создаем экземпляры прототипов
-
var mapleLeaf:Leaf = pm.make('mapleLeaf') as Leaf;
-
var birchLeaf:Leaf = pm.make('birchLeaf') as Leaf;
Сразу оговорюсь, реализована только минимально необходимая функциональность PrototypeManager все остальные «навороты» (события, предоставление списка прототипов и т.д.) вы можете дописать сами.
Каковы же результаты применения паттерна Прототип в нашем случае? Во-первых, мы ограничились всего одним действительно нужным классом Leaf, который обладает всем необходимым API, во-вторых, мы получили удобный способ создания экземпляра Leaf c заранее заданными параметрами, а также возможность добавить неограниченное количество прототипов на этапе выполнения. И, наконец, мы можем смело использовать IPrototype и PrototypeManager в следующих проектах. По-моему не плохо. А вы как считаете?
P.S.: Самым важным сложным моментом для реализации паттерна Прототип является реализация метода clone(). В AS3 для клонирования объектов можно воспользоваться копированием через ByteArray. Однако мне такой метод не кажется удачным. Например, если в классе существует внутренний счетчик экземпляров, копирование через ByteArray приведет к сбою этого счетчика. Но в любом случае реализация метода clone() зависит от клонируемого.



зачетненько
написал