Паттерн Декоратор (Decorator), реализация на as3

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

Для лучшего понимания, что собой представляет шаблон Декоратор, а я бы назвал его русским именем Matryoshka, рассмотри конкретный пример. Допустим нам необходимо расширить класс MyButton для добавления в него отступов (padding), рамки (border) и фона (background), то есть изменить отображение класса, причем добавляться элементы оформления должны как одновременно, так и по отдельности. Конечно можно добавить новые обязанности с помощью наследования, но это породит большое количество классов и будет более статическим решением, что не даст достаточной гибкости в управлении.

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

А теперь давайте рассмотрим пример реализации шаблона на actionscritp 3:

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

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

public interface IComponent
{
	function set width(value:Number):void;
	function get width():Number;

	function set height(value:Number):void;
	function get height():Number;

	//для назначения стиля
	function set decor(value:String):void;
	function get decor():String;

	function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void;
	function dispatchEvent(event:Event):Boolean;
}

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

Далее стоит создать супер классы компонентов и декораторов, важно что оба класса расширяют интерфейс IComponent, но каждый по своему:
Component.as

//супер класс компонентов, желательно сделать его internal
public class Component extends Sprite implements IComponent
{
	private var decorator:String = 'all';//для того чтобы определить какой декоратор должен быть включен, по умолчанию это все декораторы
	public static const REDRAW:String = 'component_redraw';//событие по которому обновляются декораторы
	//контсанты событий желательно выделить в отдельный класс для меньшей связаности классов

	public function set decor(value:String):void{
		this.decorator = value;
		//высылаем событие для изменения состояния декоратора
		//важно чтобы событие проходило по цепочке пузырей
		//только в этом случае все декораторы получат событие REDRAW
		this.dispatchEvent(new Event(REDRAW, true, true));
	}

	public function get decor():String{return this.decorator;}
}

Decorator.as

//супер класс декораторов, желательно сделать его internal
internal class Decorator extends Sprite implements IComponent
	{
	protected var content:IComponent;
	protected var hitSprite:Boolean;

	public function Decorator(content:IComponent)
	{
		//записываем копонент в себя
		this.content = content;
		this.addChild(this.content as DisplayObject);
		//прослушиваем компонент на его изменения
		this.addEventListener(Component.REDRAW, this.redraw);
	}

	//функция перересовки декоратора, так как при дальнейшем redraw может быть вызвано
	// и не событием Component.REDRAW, в получаемых аргументах пишем ...args
	protected function redraw(...args):void{}

	//Переопределяем полученные данные компоненту, вниз по цепочке декораторов
	override public function set width(value:Number):void{this.content.width = value;}
	override public function get width():Number{return this.content.width;}

	override public function set height(value:Number):void{this.content.width = height;}
	override public function get height():Number{return this.content.height;}

	public function set decor(value:String):void{this.content.decor = value;}
	public function get decor():String{return this.content.decor};

	override public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void{
		//тригер для назначения декоратора областью прослушивания событий мыши
		//желательно подобного не допускать, но для примера пойдет :)
		if(!hitSprite)this.content.addEventListener(type, listener, useCapture, priority, useWeakReference);
		else super.addEventListener(type, listener, useCapture, priority, useWeakReference);
	}

	override public function dispatchEvent(event:Event):Boolean{
	return this.content.dispatchEvent(event);
	}
}

Приступим к созданию конкретных классов, первый идет наш класс MyButton:

//подкласс MyButton класса Component, который надо декорировать
	public class MyButton extends Component
	{
		public function MyButton()
		{
		//просто надпись для удобства
		var label:TextField = new TextField();
		label.autoSize = TextFieldAutoSize.LEFT;
		label.selectable = false;
		this.addChild(label);
		}
	}

А теперь посмотрите насколько просты классы декораторов:
Paddingю.as

//Декоратор отступов
	public class Padding extends Decorator
	{
		private var size:int;
		private var triger:Boolean;

		public function Padding(content:IComponent){super(content);}

		override protected function redraw(...args):void{
			//если декоратор включен делаем отступы иначе возращаем все в 0
			if( this.decor == 'all' || this.decor == 'padding'){
				this.size = 5;
				//так делать не стоит но для примера я не хотел сильно
				//перегружать интерфейс и суппер класс декоратора
				this.content['x'] = this.size*2;
				this.content['y'] = this.size;
				this.triger = true;
			}else{
				this.size = 0;
				this.content['x'] = this.size;
				this.content['y'] = this.size;
				this.triger = false;
			}
		}

		//расширяет состояние
		override public function get width():Number{return this.content.width+this.size*4;}
		override public function get height():Number{return this.content.height+this.size*2;}

	}

Background.as

//Декоратор Фона
	public class Background extends Decorator
	{
		public function Background(content:IComponent){super(content);}

		override protected function redraw(...args):void{
		this.graphics.clear();
		if( this.decor == 'all' || this.decor == 'backgroud'){
		//если декоратор включен то рисуем фон
			this.graphics.beginFill(0x99cccc, 1);
			this.graphics.drawRect(0,0,this.width, this.height);
			this.graphics.endFill();
			}
		}
	}

Border.as

//Декоратор рамки
	public class Border extends Decorator
	{
		public function Border(content:IComponent){super(content);}

		override protected function redraw(...args):void{
			this.graphics.clear();
			if( this.decor == 'all' || this.decor == 'border'){
			//если декоратор включен то рисуем рамку
			this.graphics.lineStyle(1, 0x669999);
			this.graphics.drawRect(-1, -1, this.width+1, this.height+1);
			this.graphics.endFill()
			}
		}
	}

HitArea.as

//Декоратор зоны наведения мыши
	public class HitArea extends Decorator
	{
		public function HitArea(content:IComponent)
		{
		//просто включает прослушивание событий мыши
		super(content);
		this.mouseChildren = false;
		this.hitSprite = true;
		}
	}

И в конечном итоге соберем все части вместе и выведем на экран.
main.as - главный класс приложения

	public function main()
	{
		var button:IComponent = this.createButton();
		this.addChild(button as DisplayObject).x;
	}
		private function createButton():IComponent{
		//создаем декораторы и компонент и вкладываем их друг в друга
		//Думаю такой порядок их вложения логичен ине требует объяснений
		var button:IComponent = new HitArea(new Border(new Background(new Padding(new MyButton()))));
		//отсылаем событие на активацию всех декораторов
		button.dispatchEvent(new Event(Component.REDRAW, true, true));
		button.addEventListener(MouseEvent.CLICK, this.handlerClick);
		return button;
	}

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

код примера

Подведем небольшой итог:
Паттерн Декоратор (Decorator) более гибок нежели наследование, он может добавлять и удалять обязанности во время выполнения программы. При использовании же наследования придется создавать новый класс для каждой дополнительной обязанности. Кроме того декоратор позволяет добавить одно и тоже свойство дважды. Например, если добавить Border два раза, то получим двойное обрамление.
Но есть и явные недостатки, такие как множество мелких объектов, которые похожи друг на друга и различаются только способом взаимосвязи а не классом. А также детектор и его компонент не идентичны, о чем постоянно необходимо помнить.

You can leave a response, or trackback from your own site.
(3 голосов, средний: 5.00 из 5)
Загрузка ... Загрузка ...

One Response to “Паттерн Декоратор (Decorator), реализация на as3”

  1. 2morrowMan пишет:

    Объяснил хорошо! Никогда не пользовался вот так этим патерном. Но теперь точно попробую его где-то использовать :)
    Спасибо ;)

Leave a Reply

Powered by WordPress | Visit BestInCellPhones.com for Free Verizon Cell Phones | Thanks to iCellPhoneDeals.com, MMORPG Games and Conveyancing