Декоратор () е структурен шаблон за дизайн, който се използва в обектно-ориентираното програмиране.
Този шаблон може да бъде използван за разширяването на функционалността на опеределен клас по времето на изпълнение на програмата, като запазва интерфейса му. Той е много подходящ, когато се следва Принципът за еднолична отговорност, според който един клас трябва да е отговорен за една-единствена операция.
Същност
Този вид шаблон може да се използва за добавяне на функционалност към определен обект динамично, без това да засяга другите инстанции на същия клас.
Това се постига чрез създаването на нов клас Декоратор (англ. wrapper-опаковка), който „обвива“ класа. Това лесно може да се представи с примера, “опаковане на подарък, слагане на подаръка в кутия, опаковане на кутията“. Създава се поредица от обекти, която започва с декоратор обектите, отговорни за новите функционалности, и завършва с оригиналния обект..
Структура
Класовете и обектите, използвани при този модел са: Компонент (англ. Component) – интерфейсът на обектите, към които могат да се добавят допълнителни функционалности или качества динамично; Конкретен компонент (англ. ConcreteComponent) – обектът, към който ще се добавя нова функционалност; Декоратор (англ. Decorator) – пази референция към компонент обекта и създава интерфейса, който съвпада с този на компонента; Конкретен декоратор (англ. ConcreteDecorator) – прибавя нови функционалности към обекта;
Особеностите на декоратора (методи, свойства и други) обикновено се определят от интерфейса, който е еднакъв за декораторите и за декорираните обекти. В посочения пример Компонент класът е наследен едновременно от Конктерния компонент и от подкласовете на Декоратор класа (англ. ConcreteDecoratorA, англ. ConcreteDecoratorB).
Трябва да се отбележи, че декораторите и оригиналният обект имат общи свойства. В посочената диаграма примерният методdoThis() е достъпен и във вече декорираната, и в началната версия.
При този шаблон за дизайн няколко декоратора могат да бъдат използвани върху една инстанция на даден обект, като всеки от тях добавя нова функционалност.
Основание за използване Като пример, нека вземем прозореца (англ. window) в една прозоречна система (англ.windowing system). За да се разреши скролирането (англ. scrolling) през съдържанието на даден прозорец може да му се добави хоризонтална или вертикална плъзгаща лента (англ. scrollbar) в зависимост от прозореца. Нека приемем, че прозорците представляват инстанции (англ. instances) oт Прозоръчния клас (англ. Window class) и да приемем, че този клас няма функционалност (англ. functionality)за добавяне на плъзгащи ленти. В такъв случай, може да се създаде един подклас „ScrollingWindow” или „ScrollingWindowDecorator” , който добавя тази функционалност към вече съществуващия прозоречен обект (англ. Window object). На този етап и двете решения ще свършат работа.
Сега, нека приемем, че също желаем да добавим рамка (англ. borders) към този прозорец. Отново Прозоречният клас не поддържа такава функционалност. Подкласът „ ScrollingWindow”представлява проблем, защото ефективно създава нов вид прозорец. Ако трябва да се добави функционалност за рамки на много прозорци, но не и на всички, трябва да се създадат подкласове „WindowWithBorder” /прозорец с рамка/ и „ScrollingWindowWithBorder” /прозорец с плъзгаща лента и рамка/. Проблемът се влошава с добавянето на всяко едно допълнително свойство или прозоречен подтип. За декоративни решения трябва само да се добави „BorderedWindowDecorator” /декоратор на прозоречните рамки/, когато вече програмата е инициализирана (англ. runtime), може да се декорират съществуващите прозорци със „ScrollingWindowDecorator” или „BorderedWindowDecorator” , или и с двете, по преценка. Забележете, че ако се изисква да се добави една функционалност към всички прозорци, трябва само да се промени основния клас (англ. base class) . От друга страна , понякога / ако се използва външен софтуер (англ. framework)/ това е невъзможно да се направи, непозволено е или в случая е неподходящо да се промени основният клас.
Пояснение – в предишния пример, класовете „SimpleWindow” и „WindowDecorator” имплементират интерфейсът (англ. interface) на „Window”,което дефинира методите „draw()” и „getDescription()”, които са необходими в разиграния сценарий, за да се декорира прозоречен контрол.
Оценка Декораторът е удобна алтернатива на наследяването. При наследяването добавянето на функционалност става при компилация и промените се отнасят за всички инстанции на оригиналния клас. Декораторът от своя страна добавя функционалност към даден обект динамично т.е по време на изпълнение на програмата, без да променя неговата структура.
Посочват се две главни предимства и два недостатъка на декоратор шаблона : Повече гъвкавост от колкото при статично наследяване. При някои обектно-ориентирани езици, създаването на класове по време на изпълнение е невъзможно и обикновено по време на компилация не може да се предвидят какви комбинации от нови функционалности ще са нужни. Това би означавало да се създава нов клас за всяка възможна комбинация. Това може да доведе до създаването на много класове и да усложни структурата на системата. Декораторите, от своя страна, са обекти, създадени по време на изпълнение на програмата и могат да се комбинират според всяка нужда. Избягва поставянето на прекалено много свойства в класовете, които са по-високо в йерархията. Декораторът, както вече беше споменато, прибавя нужните функционалности и съответно исканите комбинации. Вместо да се предвидят и поддържат всички свойства и характеристики чрез сложна поредица от отделни класове, може да се създаде обикновен клас, към който последователно да се добавят свойства чрез декоратор класовете. Така даденото приложение не се усложнява с особености, от които няма нужда. Декораторът и неговия компонент не са идентични. Декораторът е просто обвивка. От гледна точка на програмната логика, клас с добавен декоратор е различен от оригиналния клас. В този смисъл не може да се разчита на типа обект, когато се използват декоратори. Наличието на много малки обекти. Дизайн, който използва декоратор, често съдържа множество малки обекти, които изглеждат по един и същи начин. Тези обекти се различават по начина, по който са свързани помежду си, а не по класа им или по стойностите на техните променливи. Оттук идва и трудността при изучаването и разбирането им.
Примери C# Пример за Декоратор C# 3.0 Design Patterns – Judith Bishop using System; using System.Collections.Generic; using System.Text;
class DecoratorPatterns {
// Decorator Pattern програмен шаблон за Декоратор – автор Judith Bishop Dec 2006 // Показва 2 декоратора и изводът от различни компбинации // на декоратори върху основния компонент
interface IComponent { string Operation ();
class Component : IComponent { public string Operation ()
return "I am walking "; //Аз вървя
class DecoratorA : IComponent { IComponent component;
public DecoratorA (IComponent c )
component = c;
public string Operation ()
string s = component.Operation (); s += "and listening to Classic FM "; //и слушам радио Классик return s;
class DecoratorB : IComponent { IComponent component; public string addedState = "past the Coffee Shop "; //вече съм минал покрай кафето
public DecoratorB (IComponent c )
component = c;
public string Operation ()
string s = component.Operation (); s += "to school "; //към училище return s;
public string AddedBehavior () //прибавено поведение
return "and I bought a cappuccino "; //и си купих капучино
} //eof class
class Client
static void Display (string s, IComponent c )
Console.WriteLine (s + c.Operation () ); } //eof method
static void Main ()
Console.WriteLine ("Decorator Pattern\n" );
IComponent component = new Component (); Display ("1. Basic component: ", component ); Display ("2. A-decorated : ", new DecoratorA (component ) ); Console.ReadLine (); //изчакай удар по конзолата от потребителя Display ("3. B-decorated : ", new DecoratorB (component ) ); Console.ReadLine (); Display ("4. B-A-decorated : ", new DecoratorB (new DecoratorA (component ) ) ); // Explicit DecoratorB Console.ReadLine (); DecoratorB b = new DecoratorB (new Component () ); Display ("5. A-B-decorated : ", new DecoratorA (b ) ); // Invoking its added state and added behavior Console.WriteLine ("\t\t\t" + b.addedState + b.AddedBehavior () ); Console.ReadLine (); } //eof Main } //eof class Client } //eof class DecoratorPatterns /* Output --- изводът на информация по конзолният прозорец Decorator Pattern
1. Basic component: I am walking 2. A-decorated : I am walking and listening to Classic FM - 3. B-decorated : I am walking to school 4. B-A-decorated : I am walking and listening to Classic FM to school 5. A-B-decorated : I am walking to school and listening to Classic FM past the Coffee Shop and I bought a cappuccino * /
Java public class Testing {
public static void main(String[] args) { // Създаваме два нови обекта от класовете Name и NameWrapper Name name = new Name(); NameWrapper testWrapper = new NameWrapper(name); /* Принтираме резултатът от методът getName() при двата обекта (оригиналният и "обвитият") */ System.out.println(name.getName()); System.out.println(testWrapper.getName()); } // Създаваме клас наречен Name с два метода public static class Name { // Задаваме първоначална стойност на името private String name = "Иван"; // Метод, който ни дава възможността да променим името (неговата стойност) public void setName(String name) { this.name = name; } // Метод, даващ ни името (неговата стойност) public String getName() { return name;
/* Създаваме клас наречен NameWrapper, който "обвива" класът Name и му придава нови функции */ public static class NameWrapper extends Name { // Създаваме променлива от тип Name private Name name; /* В нашия конструктор задаваме стойността на променливата ни name да е същата като на обектът който сме дали като параметър на конструкторът*/ public NameWrapper(Name name1) { this.name = name1; } /* Пренаписваме методът от клас Name за да можем да ползваме неговите функции от нашия NameWrapper клас */ @Override public void setName(String name) { this.name.setName(name); } /* Пренаписваме методът от клас Name и му добавяме допълнителна функционалност */ @Override public String getName() { // Добавяме допълнителен текст return "Името е: " + name.getName() + ".";
/* Резултатът изписан на конзолата след изпълнението на програмата: При оригиналния обект: Иван При "обвитият" обект: Името е: Иван. * /
PHP Когато трябва да добавим уникални особености към даден клас, една добра алтернатива на наследяването е да използваме декоратор. Следващият пример обяснява какви са предимствата на втория подход.
Ето клас, който генерира съдържание за имейл. В долния блок ясно се вижда, че за генериране на стандартни съобщения, класът ще функционира добре.
class eMailBody { private $header = 'This is email header'; private $footer = 'This is email Footer'; public $body = '';
public function loadBody() { $this->body .= "This is Main Email body.<br />";
Да приемем, че идва Коледа и искаме да изпратим поздравително съобщение на някого. За целта една опция е директно да променим горния клас, което не искаме да правим, защото той работи добре. Може да постигнем същия ефект с наследяване. За целта създаваме дъщерен клас, който добавя допълнителното съдържание:
class christmasEmail extends eMailBody { public function loadBody() { parent::loadBody(); $this->body .= "Added Content for Xmas<br />";
$christmasEmail = new christmasEmail(); $christmasEmail->loadBody(); echo $christmasEmail->body;
Готови сме с новия код, но няколко дни по-късно идва Нова Година и на нас ни трябва още един дъщерен клас.
class newYearEmail extends eMailBody { public function loadBody() { parent::loadBody(); $this->body .= "Added Content for New Year<br />";