поиск

Клонирование в мирных целях — знакомство с паттерном Прототип (Prototype)

Намусорил: Алексей «Vooparker» Аникутин
В категории: ActionScript 3, Теория разработки

Наверно, как и большинство из вас познакомившись с объектно-ориентированным подходом в программировании, я стал видеть классы всюду и везде, причем порой, даже там, где в них и не было необходимости. Очень быстро мои приложения стали просто изобиловать классами, которые в большинстве своем не сильно отличались друг от друга (если вообще отличались). Сегодня я предлагаю поговорить о средстве, которое гарантированно поможет вам избавиться от «лишних» классов и в тоже время внесет стройности в ваши приложения. Безусловно, я уверен, что среди читателей Garbage Collector’а есть те, кто знаком с практикой описанной в этой статье (от них я жду комментариев), но также уверен, что кому-то этот материал окажется новым и полезным. Ну вот, со вступлением покончено, теперь в бой.

За окном осень вот-вот вступит в свои права, так что пример я предлагаю взять осенний — программа, имитирующая листопад. В задачи программы входят генерация листьев и последующее управление ими. Так как все листья обладают схожими качествами, мы определяем базовый класс Leaf, описав в нем вид дерева, которому лист принадлежит, размер и оттенок (методы и прочие свойства я показывать не буду):

Actionscript:
  1. package
  2. {
  3.     public class Leaf
  4.     {
  5.         public var type:String;  
  6.         public var size:String;
  7.         public var tint:String;
  8.        
  9.         public function Leaf (type:String, size:String, tint:String)
  10.         {
  11.             this.type = type;
  12.             this.size = size;
  13.             this.tint = tint;
  14.         }
  15.        
  16.         // прочие методы и свойства
  17.     }
  18. }

Теперь мы знаем, что наш листопад происходит в лесу, где растут, например, клены и березы. А так же мы знаем, что кленовые листья большие и темные, а березовые – маленькие и светлые. То есть параметр type определяет остальные два параметра. Логичным на первый взгляд, кажется скрыть эту зависимость в подклассах Leaf: MapleLeaf и BirchLeaf:

Actionscript:
  1. package
  2. {
  3.     public class MapleLeaf extends Leaf
  4.     {
  5.         public function MapleLeaf ()
  6.         {
  7.             super('maple', 'big', 'dark')
  8.         }
  9.     }
  10. }

Actionscript:
  1. package
  2. {
  3.     public class BirchLeaf extends Leaf
  4.     {
  5.         public function BirchLeaf ()
  6.         {
  7.             super('birch', 'small', 'light');
  8.         }
  9.     }
  10. }

А теперь давайте посмотрим на наши новые классы внимательно. Отличаются ли они чем-то от базового класса Leaf? В целом — нет, с точки зрения API это абсолютно идентичные классы, если не считать создание экземпляров. К тому же с таким «классовым» подходом в случае добавления нового вида листьев нам придется вводить каждый раз новый подкласс Leaf. А если вы захотите добавить интерактивности в приложение в виде возможности пользователю самому создавать новые виды листьев с произвольными значениями своих свойств на этапе выполнения приложения, тогда мы просто не можем создать новый класс? Думаю, негибкость создания подклассов в приведенном примере очевидна.

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

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

Как я уже сказал, паттерн основывается на клонировании существующих экземпляров. Таким образом, у любого прототипа должен присутствовать метод clone() который должен возвращать точную копию экземпляра. Т. к. каждый клонируемый объект обладает свойственной его классу структурой, то создать базовый класс с реализованным в нем методом clone() не получится. Да и к тому же вполне возможно, что класс, который вы собираетесь наделить способностью клонироваться, уже от кого-то наследуется. Поэтому мы определяем интерфейс IPrototype (ЙаПрототип:)), который должны реализовывать все классы, экземпляры которых мы будем клонировать:

Actionscript:
  1. package
  2. {
  3.     public interface IPrototype
  4.     {
  5.         function clone ():IPrototype;
  6.     }
  7. }

Теперь модифицируем наш класс Leaf реализовав в нем метод clone():

Actionscript:
  1. package
  2. {
  3.     public class Leaf implements IPrototype
  4.     {
  5.         public var type:String;
  6.         public var size:String;
  7.         public var tint:String;
  8.  
  9.         public function Leaf (type:String, size:String, tint:String)
  10.         {
  11.             this.type = type;
  12.             this.size = size;
  13.             this.tint = tint;
  14.         }
  15.  
  16.         public function clone ():IPrototype
  17.         {
  18.             return new Leaf(type, size, tint);
  19.         }
  20.         // прочие методы и свойства
  21.     }
  22. }

Теперь мы можем создать «кленовый» экземпляр класса Leaf и в случае чего его клонировать:

Actionscript:
  1. var mapleLeaf:Leaf = new Leaf('maple', 'big', 'dark');
  2. var anotherMapleLeaf:Leaf = mapleLeaf.clone() as Leaf;

Так как возвращаемый методом clone() тип объявлен как IPrototype, мы приводим к нужному типу с помощью оператора as.

С первой задачей мы справились, у нас всего на всего один класс. Теперь реализуем удобный способ создания новых экземпляров на базе прототипов, а также возможность создавать и удалять прототипы на этапе исполнения. Для этого создадим класс PrototypeManager, который должен хранить в себе прототипы, предоставлять возможность регистрации и удаления прототипов, и главное, создавать новые экземпляры. Чтобы получить доступ к тому или иному прототипу, каждый из них будет иметь уникальный строковый идентификатор. И так, давайте посмотрим, как может выглядеть реализация PrototypeManager:

Actionscript:
  1. package
  2. {
  3.     public class PrototypeManager
  4.     {
  5.         private var __prototypes:Object;
  6.  
  7.         public function PrototypeManager ()
  8.         {
  9.             __prototypes = {};
  10.         }
  11.  
  12.         public function addPrototype (prototypeName:String, prototype:IPrototype, overridePrecursor:Boolean=false):Boolean
  13.         {
  14.             if(__prototypes[prototypeName] == undefined || overridePrecursor)
  15.             {
  16.                 __prototypes[prototypeName] = prototype;
  17.                 return true;
  18.             }
  19.             else
  20.             {
  21.                 return false;
  22.             }
  23.         }
  24.  
  25.         public function removePrototype (prototypeName:String):Boolean
  26.         {
  27.             if(__prototypes[prototypeName] != undefined)
  28.             {
  29.                 delete __prototypes[prototypeName];
  30.                 return true;
  31.             }
  32.             else
  33.             {
  34.                 return false;
  35.             }
  36.         }
  37.  
  38.         public function make (prototypeName:String):IPrototype
  39.         {
  40.             if(__prototypes[prototypeName] != undefined)
  41.                 return (__prototypes[prototypeName] as IPrototype).clone();
  42.             else
  43.                 return null;
  44.         }
  45.     }
  46. }

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

Actionscript:
  1. // Создаем экземпляр PrototypeManager
  2. var pm:PrototypeManager = new PrototypeManager();
  3.  
  4. // Добавляем прототипные экземпляры в пул менеджера под уникальными именами
  5. pm.addPrototype('mapleLeaf', new Leaf('maple', 'big', 'dark'));
  6. pm.addPrototype('birchLeaf', new Leaf('birch', 'small', 'light'));
  7.  
  8. // Создаем экземпляры прототипов
  9. var mapleLeaf:Leaf = pm.make('mapleLeaf') as Leaf;
  10. var birchLeaf:Leaf = pm.make('birchLeaf') as Leaf;

Сразу оговорюсь, реализована только минимально необходимая функциональность PrototypeManager все остальные «навороты» (события, предоставление списка прототипов и т.д.) вы можете дописать сами.

Каковы же результаты применения паттерна Прототип в нашем случае? Во-первых, мы ограничились всего одним действительно нужным классом Leaf, который обладает всем необходимым API, во-вторых, мы получили удобный способ создания экземпляра Leaf c заранее заданными параметрами, а также возможность добавить неограниченное количество прототипов на этапе выполнения. И, наконец, мы можем смело использовать IPrototype и PrototypeManager в следующих проектах. По-моему не плохо. А вы как считаете? ;)

P.S.: Самым важным сложным моментом для реализации паттерна Прототип является реализация метода clone(). В AS3 для клонирования объектов можно воспользоваться копированием через ByteArray. Однако мне такой метод не кажется удачным. Например, если в классе существует внутренний счетчик экземпляров, копирование через ByteArray приведет к сбою этого счетчика. Но в любом случае реализация метода clone() зависит от клонируемого.



Kомментариев - 3 к «Клонирование в мирных целях — знакомство с паттерном Прототип (Prototype)»

nouba [31 августа, 2007 в 11:05]

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

Антон Волков [28 сентября, 2007 в 00:05]

Вот это приведение типов после клонирования меня напрягает жуть как.
Постоянно с этим сталкиваюсь. Ничего умнее не придумал.
Вот была бы возможность перекрывать методы с учётом совместимости!

Туц [17 августа, 2008 в 12:48]

Спасибо, впрыскивание помогло

Написать комментарий:

 

Bы можете использовать следующие теги для форматирования: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>



User's collector

Внимание!
Эта опция станет доступной только после того как вы авторизуетесь.


 запомнить меня 
Я новый пользователь

На правах рекламы