Декоратор (шаблон проектирования): различия между версиями
[непроверенная версия] | [непроверенная версия] |
агрегация не так хороша, как декоратор |
→C++: Добавлена демонстрация того, что для применения изменений не обязательно вызывать операцию у конкретного декоратора, а достаточно вызвать у интерфейса. |
||
(не показана 31 промежуточная версия 24 участников) | |||
Строка 1: | Строка 1: | ||
{{Не путать|Python#Декораторы|декораторами функций или классов в Python|их концепция и концепция описываемого в этой статье шаблона проектирования отличаются}} |
|||
{{Значения|Декоратор}} |
{{Значения|Декоратор}} |
||
{{Шаблон проектирования |
{{Шаблон проектирования |
||
Строка 34: | Строка 35: | ||
=== Реализация === |
=== Реализация === |
||
Создаётся абстрактный класс, представляющий как исходный класс, так и новые, добавляемые в класс функции. В классах-декораторах новые функции вызываются в требуемой последовательности — до или после вызова последующего объекта. |
|||
При желании остаётся возможность использовать исходный класс (без расширения функциональности), если на его объект сохранилась ссылка. |
При желании остаётся возможность использовать исходный класс (без расширения функциональности), если на его объект сохранилась ссылка. |
||
Строка 42: | Строка 43: | ||
* Базовые классы языка [[Java]] широко используют шаблон Декоратор для организации обработки операций ввода-вывода. |
* Базовые классы языка [[Java]] широко используют шаблон Декоратор для организации обработки операций ввода-вывода. |
||
* И декоратор, и [[Адаптер (шаблон проектирования)|адаптер]] являются обёртками вокруг объекта — хранят в себе [[Ссылка (программирование)|ссылку]] на оборачиваемый объект и часто передают в него вызовы методов. Отличие декоратора от адаптера в том, что адаптер имеет внешний интерфейс, отличный от интерфейса оборачиваемого объекта, и используется именно для стыковки разных интерфейсов. Декоратор же имеет точно такой же интерфейс, и используется для добавления функциональности. |
* И декоратор, и [[Адаптер (шаблон проектирования)|адаптер]] являются обёртками вокруг объекта — хранят в себе [[Ссылка (программирование)|ссылку]] на оборачиваемый объект и часто передают в него вызовы методов. Отличие декоратора от адаптера в том, что адаптер имеет внешний интерфейс, отличный от интерфейса оборачиваемого объекта, и используется именно для стыковки разных интерфейсов. Декоратор же имеет точно такой же интерфейс, и используется для добавления функциональности. |
||
* Для расширения функциональности класса возможно использовать как декораторы, так и [[Стратегия (шаблон проектирования)|стратегии]]. Декораторы оборачивают объект снаружи, стратегии же вставляются в него внутрь по неким интерфейсам. |
* Для расширения функциональности класса возможно использовать как декораторы, так и [[Стратегия (шаблон проектирования)|стратегии]]. Декораторы оборачивают объект снаружи, стратегии же вставляются в него внутрь по неким интерфейсам. |
||
** Недостаток стратегии: класс должен быть спроектирован с возможностью вставления стратегий, декоратор же не требует такой поддержки. |
** Недостаток стратегии: класс должен быть спроектирован с возможностью вставления стратегий, декоратор же не требует такой поддержки. |
||
** Недостаток декоратора: он оборачивает ровно тот же интерфейс, что предназначен для внешнего мира, что вызывает смешение публичного интерфейса и интерфейса кастомизации, которое не всегда желательно. |
** Недостаток декоратора: он оборачивает ровно тот же интерфейс, что предназначен для внешнего мира, что вызывает смешение публичного интерфейса и интерфейса кастомизации, которое не всегда желательно. |
||
== Применение шаблона == |
== Применение шаблона == |
||
Драйверы-фильтры в ядре [[Windows]] (архитектура [[Windows Driver Model|WDM (Windows Driver Model)]]) представляют собой декораторы. Несмотря на то, что WDM реализована на не-объектном языке [[Си (язык программирования)|Си]], в ней |
Драйверы-фильтры в ядре [[Windows]] (архитектура [[Windows Driver Model|WDM (Windows Driver Model)]]) представляют собой декораторы. Несмотря на то, что WDM реализована на не-объектном языке [[Си (язык программирования)|Си]], в ней чётко прослеживаются паттерны проектирования — декоратор, [[цепочка обязанностей]], и [[Команда (шаблон проектирования)|команда]] (объект [[IRP]]). |
||
Архитектура [[Component Object Model|COM (Component Object Model)]] не поддерживает наследование реализаций, вместо него предлагается использовать декораторы (в данной архитектуре это называется «агрегация» |
Архитектура [[Component Object Model|COM (Component Object Model)]] не поддерживает наследование реализаций, вместо него предлагается использовать декораторы (в данной архитектуре это называется «агрегация»). При этом архитектура решает (с помощью механизма pUnkOuter) проблему object identity, возникающую при использовании декораторов — identity агрегата есть identity его самого внешнего декоратора. |
||
== Примеры == |
== Примеры == |
||
=== Kotlin === |
|||
{{Скрытый блок|header=Пример на языке Kotlin|content=<source lang="kotlin"> |
|||
fun main() { |
|||
LoggingNotifier( |
|||
FancyNotifier( |
|||
ConsoleNotifier() |
|||
) |
|||
).notify("Hello, World!") |
|||
} |
|||
interface Notifier { |
|||
fun notify(message: String) |
|||
} |
|||
class ConsoleNotifier : Notifier { |
|||
override fun notify(message: String) { |
|||
println(message) |
|||
} |
|||
} |
|||
class LoggingNotifier(private val notifier: Notifier) : Notifier { |
|||
override fun notify(message: String) { |
|||
notifier.notify(message) |
|||
println("LOG - $message") // Like a logger |
|||
} |
|||
} |
|||
class FancyNotifier(private val notifier: Notifier) : Notifier { |
|||
override fun notify(message: String) { |
|||
val border = "-".repeat(message.length) |
|||
notifier.notify(""" |
|||
$border |
|||
$message |
|||
$border |
|||
""".trimIndent()) |
|||
} |
|||
} |
|||
</source>}} |
|||
=== Ruby === |
|||
{{Hider| |
|||
title = '''Пример на языке [[Ruby]]''' | |
|||
content-style = text-align: left; | |
|||
hidden = true | |
|||
content = |
|||
<source lang="ruby"> |
|||
module DecoratorPattern |
|||
# Extends basic functionallity combining several Decorators |
|||
class Source |
|||
def initialize(line) |
|||
@line = line |
|||
end |
|||
def write_line |
|||
@line |
|||
end |
|||
end |
|||
# Abstract Decorator |
|||
module Decorator |
|||
def initialize(source) |
|||
@source = source |
|||
end |
|||
def write_line |
|||
raise NotImplementedError |
|||
end |
|||
end |
|||
# Concrete Decorator |
|||
class Upcaser |
|||
include Decorator |
|||
def write_line |
|||
@source.write_line.upcase |
|||
end |
|||
end |
|||
# Concrete Decorator |
|||
class Timestamper |
|||
include Decorator |
|||
def write_line |
|||
"#{Time.now.strftime('%H:%m')} #{@source.write_line}" |
|||
end |
|||
end |
|||
# Concrete Decorator |
|||
class Datestamper |
|||
include Decorator |
|||
def write_line |
|||
"#{Time.now.strftime('%d.%m.%y')} #{@source.write_line}" |
|||
end |
|||
end |
|||
def self.run |
|||
puts '=> Decorator' |
|||
source = Source.new('Lorem ipsum dolor sit amet') |
|||
puts "Source:\n=> #{source.write_line}" |
|||
upcased = Upcaser.new(source) |
|||
puts "Upcased:\n=> #{upcased.write_line}" |
|||
timestamped = Timestamper.new(source) |
|||
puts "Timestamped:\n=> #{timestamped.write_line}" |
|||
datestamped = Datestamper.new(source) |
|||
puts "Datestamped:\n=> #{datestamped.write_line}" |
|||
upcased_timestamped = Timestamper.new(Upcaser.new(source)) |
|||
puts "Upcased and timestamped:\n=> #{upcased_timestamped.write_line}" |
|||
upcased_datestamped_timestamped = Datestamper.new(Timestamper.new(Upcaser.new(source))) |
|||
puts "Upcased, datestamped and timestamped:\n=> #{upcased_datestamped_timestamped.write_line}" |
|||
datestamped_timestamped = Datestamper.new(Timestamper.new(source)) |
|||
puts "Datestamped and timestamped:\n=> #{datestamped_timestamped.write_line}" |
|||
puts '' |
|||
end |
|||
end |
|||
DecoratorPattern.run |
|||
# => Decorator |
|||
# Source: |
|||
# => Lorem ipsum dolor sit amet |
|||
# Upcased: |
|||
# => LOREM IPSUM DOLOR SIT AMET |
|||
# Timestamped: |
|||
# => 18:03 Lorem ipsum dolor sit amet |
|||
# Datestamped: |
|||
# => 29.03.19 Lorem ipsum dolor sit amet |
|||
# Upcased and timestamped: |
|||
# => 18:03 LOREM IPSUM DOLOR SIT AMET |
|||
# Upcased, datestamped and timestamped: |
|||
# => 29.03.19 18:03 LOREM IPSUM DOLOR SIT AMET |
|||
# Datestamped and timestamped: |
|||
# => 29.03.19 18:03 Lorem ipsum dolor sit amet |
|||
</source> |
|||
}} |
|||
=== Java === |
=== Java === |
||
Строка 89: | Строка 235: | ||
} |
} |
||
class DecoratorSpace extends Decorator{ |
class DecoratorSpace extends Decorator { |
||
public DecoratorSpace(InterfaceComponent c) { |
public DecoratorSpace(InterfaceComponent c) { |
||
Строка 119: | Строка 265: | ||
} |
} |
||
@Override |
|||
public void newOperation() { |
public void newOperation() { |
||
System.out.println("New comma operation"); |
System.out.println("New comma operation"); |
||
Строка 285: | Строка 431: | ||
=== C++ === |
=== C++ === |
||
{{Hider| |
{{Hider| |
||
title = '''Пример на языке |
title = '''Пример на языке C++''' | |
||
content-style = text-align: left; | |
content-style = text-align: left; | |
||
hidden = true | |
hidden = true | |
||
Строка 296: | Строка 442: | ||
public: |
public: |
||
virtual void operation() = 0; |
virtual void operation() = 0; |
||
virtual ~IComponent(){} |
|||
}; |
}; |
||
Строка 310: | Строка 456: | ||
public: |
public: |
||
DecoratorOne(IComponent |
DecoratorOne(std::shared_ptr<IComponent> component): m_component(component) {} |
||
virtual void operation() { |
virtual void operation() { |
||
Строка 322: | Строка 468: | ||
public: |
public: |
||
DecoratorTwo(IComponent |
DecoratorTwo(std::shared_ptr<IComponent> component): m_component(component) {} |
||
virtual void operation() { |
virtual void operation() { |
||
Строка 329: | Строка 475: | ||
} |
} |
||
}; |
}; |
||
void Operation(IComponent &component) { |
|||
component.operation(); |
|||
} |
|||
int main() { |
int main() { |
||
DecoratorTwo obj( |
DecoratorTwo obj(std::make_shared<DecoratorOne>(std::make_shared<Component>())); |
||
Operation(obj); // prints "Hello, World!\n" |
|||
return 0; |
return 0; |
||
Строка 346: | Строка 496: | ||
import std.stdio; |
import std.stdio; |
||
abstract class |
abstract class Figure |
||
{ |
{ |
||
protected |
protected string name; |
||
string name; |
|||
string getInfo(); |
|||
public: |
|||
string GetInfo(); |
|||
} |
} |
||
class Empty : Figure |
|||
{ |
{ |
||
override string getInfo() |
|||
public: |
|||
{ |
|||
string GetInfo() |
|||
return null; |
|||
} |
|||
}; |
|||
} |
} |
||
class |
class Circle : Figure |
||
{ |
{ |
||
protected |
protected Figure figure; |
||
TFigure figure; |
|||
this(Figure f) |
|||
{ |
|||
figure = f; |
|||
name = " circle "; |
|||
} |
|||
figure = theFigure; |
|||
name = " circle "; |
|||
override string getInfo() |
|||
{ |
|||
return name ~ figure.getInfo(); |
|||
string GetInfo() |
|||
} |
|||
return (name ~ figure.GetInfo()); |
|||
} |
|||
} |
} |
||
class |
class Bar : Figure |
||
{ |
{ |
||
protected |
protected Figure figure; |
||
TFigure figure; |
|||
this(Figure f) |
|||
{ |
|||
figure = f; |
|||
name = " bar "; |
|||
} |
|||
figure = theFigure; |
|||
name = " bar "; |
|||
override string getInfo() |
|||
{ |
|||
return figure.getInfo() ~ name; |
|||
string GetInfo() |
|||
} |
|||
return (figure.GetInfo() ~ name); |
|||
} |
|||
} |
} |
||
void main() |
void main() |
||
{ |
{ |
||
Figure figures = new Bar(new Circle(new Bar(new Circle(new Empty())))); |
|||
writeln(figures. |
writeln(figures.getInfo()); |
||
} |
} |
||
</source> |
</source> |
||
Строка 410: | Строка 553: | ||
=== Python === |
=== Python === |
||
Ниже — пример реализации шаблона проектирования. В Python существуют [[Python#Декораторы|декораторы функций и классов]], концепция которых отличается от концепции шаблона проектирования. |
|||
{{Hider| |
{{Hider| |
||
title = '''Пример на языке Python'''<ref>{{Cite web|url=https://wiki.python.org/moin/DecoratorPattern|title=Decorator Pattern|website=wiki.python.org|access-date=2021-10-24|archive-date=2021-10-24|archive-url=https://web.archive.org/web/20211024132841/https://wiki.python.org/moin/DecoratorPattern|deadlink=no}}</ref> | |
|||
title = '''Пример на языке Python''' | |
|||
content-style = text-align: left; | |
content-style = text-align: left; | |
||
hidden = true | |
hidden = true | |
||
content = |
content = |
||
<source lang=" |
<source lang="python3"> |
||
""" |
|||
from abc import ABCMeta, abstractmethod |
|||
Demonstrated decorators in a world of a 10x10 grid of values 0-255. |
|||
""" |
|||
import random |
|||
class IOperator(object): |
|||
""" |
|||
Интерфейс, который должны реализовать как декоратор, |
|||
так и оборачиваемый объект. |
|||
""" |
|||
__metaclass__ = ABCMeta |
|||
def s32_to_u16( x ): |
|||
@abstractmethod |
|||
if x < 0: |
|||
sign = 0xf000 |
|||
else: |
|||
sign = 0 |
|||
bottom = x & 0x00007fff |
|||
return bottom | sign |
|||
def seed_from_xy( x,y ): return s32_to_u16( x ) | (s32_to_u16( y ) << 16 ) |
|||
class |
class RandomSquare: |
||
def __init__( s, seed_modifier ): |
|||
"""Компонент программы""" |
|||
s.seed_modifier = seed_modifier |
|||
def operator(self): |
|||
def get( s, x,y ): |
|||
seed = seed_from_xy( x,y ) ^ s.seed_modifier |
|||
random.seed( seed ) |
|||
return random.randint( 0,255 ) |
|||
class DataSquare: |
|||
def __init__( s, initial_value = None ): |
|||
s.data = [initial_value]*10*10 |
|||
def get( s, x,y ): |
|||
return s.data[ (y*10)+x ] # yes: these are all 10x10 |
|||
def set( s, x,y, u ): |
|||
s.data[ (y*10)+x ] = u |
|||
class |
class CacheDecorator: |
||
def __init__( s, decorated ): |
|||
"""Декоратор""" |
|||
s.decorated = decorated |
|||
def __init__(self, obj): |
|||
s.cache = DataSquare() |
|||
def get( s, x,y ): |
|||
if s.cache.get( x,y ) == None: |
|||
s.cache.set( x,y, s.decorated.get( x,y ) ) |
|||
return s.cache.get( x,y ) |
|||
class MaxDecorator: |
|||
def operator(self): |
|||
def __init__( s, decorated, max ): |
|||
return self.obj.operator() + 5.0 |
|||
s.decorated = decorated |
|||
s.max = max |
|||
def get( s, x,y ): |
|||
if s.decorated.get( x,y ) > s.max: |
|||
return s.max |
|||
return s.decorated.get( x,y ) |
|||
class MinDecorator: |
|||
def __init__( s, decorated, min ): |
|||
s.decorated = decorated |
|||
s.min = min |
|||
def get( s, x,y ): |
|||
if s.decorated.get( x,y ) < s.min: |
|||
return s.min |
|||
return s.decorated.get( x,y ) |
|||
class VisibilityDecorator: |
|||
def __init__( s, decorated ): |
|||
s.decorated = decorated |
|||
def get( s,x,y ): |
|||
return s.decorated.get( x,y ) |
|||
def draw(s ): |
|||
for y in range( 10 ): |
|||
for x in range( 10 ): |
|||
print "%3d" % s.get( x,y ), |
|||
print |
|||
# Now, build up a pipeline of decorators: |
|||
random_square = RandomSquare( 635 ) |
|||
random_cache = CacheDecorator( random_square ) |
|||
max_filtered = MaxDecorator( random_cache, 200 ) |
|||
min_filtered = MinDecorator( max_filtered, 100 ) |
|||
final = VisibilityDecorator( min_filtered ) |
|||
final.draw() |
|||
</source> |
|||
Выходные данные (учтите использование генератора псевдослучайных чисел): |
|||
<source lang="python3"> |
|||
comp = Component() |
|||
100 100 100 100 181 161 125 100 200 100 |
|||
comp = Wrapper(comp) |
|||
200 100 100 200 100 200 200 184 162 100 |
|||
print comp.operator() |
|||
155 100 200 100 200 200 100 200 143 100 |
|||
# 15.0 |
|||
100 200 144 200 101 143 114 200 166 136 |
|||
100 147 200 200 100 100 200 141 172 100 |
|||
144 161 100 200 200 200 190 125 100 177 |
|||
150 200 100 175 111 195 193 128 100 100 |
|||
100 200 100 200 200 129 159 105 112 100 |
|||
100 101 200 200 100 100 200 100 101 120 |
|||
180 200 100 100 198 151 100 195 131 100 |
|||
</source> |
</source> |
||
}} |
}} |
||
Строка 614: | Строка 819: | ||
content = |
content = |
||
Шаблон декоратор в языках с динамической типизацией может быть |
Шаблон декоратор в языках с динамической типизацией может быть применён без интерфейсов и традиционного для ООП наследования. |
||
Этот пример скопирован с английской версии статьи. |
Этот пример скопирован с английской версии статьи. Расчёт стоимости кофе: |
||
<source lang="javascript"> |
<source lang="javascript"> |
||
// ConcreteComponent (класс для последующего декорирования) |
// ConcreteComponent (класс для последующего декорирования) |
||
Строка 658: | Строка 863: | ||
</source> |
</source> |
||
Реализация имеющегося выше C# примера. В ConcreteComponent добавлена локальная переменная price, которая будет изменяться как в |
Реализация имеющегося выше C# примера. В ConcreteComponent добавлена локальная переменная price, которая будет изменяться как в нём самом, так и декораторах. Имена классов (кроме постфиксов "A" и "B") совпадают с именами участников шаблона. |
||
<source lang="javascript"> |
<source lang="javascript"> |
||
function Component() { |
function Component() { |
||
Строка 1008: | Строка 1213: | ||
ReadLn; |
ReadLn; |
||
end. |
end. |
||
</source> |
|||
}} |
|||
=== Swift === |
|||
{{Hider| |
|||
title = '''Пример на языке Swift''' | |
|||
content-style = text-align: left; | |
|||
hidden = true | |
|||
content = |
|||
<source lang=swift> |
|||
protocol Book { |
|||
var title: String { get set } |
|||
var price: Int { get set } |
|||
func getPrice() -> Int |
|||
} |
|||
class BookImpl: Book { |
|||
var title: String = "" |
|||
var price: Int = 1000 |
|||
func getPrice() -> Int { |
|||
return price |
|||
} |
|||
} |
|||
class DiscountBook: Book { |
|||
let element: BookImpl |
|||
var title: String = "«Грокаем алгоритмы»" |
|||
var price: Int = 0 |
|||
init(element: BookImpl) { |
|||
self.element = element |
|||
self.title = element.title |
|||
self.price = element.price |
|||
} |
|||
// 30% sale |
|||
func getPrice() -> Int { |
|||
return price - (price * 30)/100 |
|||
} |
|||
} |
|||
// Use Decorator |
|||
let book = BookImpl() |
|||
let discountBook = DiscountBook(element: book) |
|||
print(discountBook.getPrice()) |
|||
</source> |
</source> |
||
}} |
}} |
||
Строка 1024: | Строка 1282: | ||
}} |
}} |
||
* {{Книга|автор=Эрик Фримен, Элизабет Фримен|заглавие=Паттерны проектирования = Head First Desing Patterns|ответственный=|издание=|место=СПб|издательство=Питер|год=|страницы=|страниц=656|isbn=978-5-459-00435-9}} |
* {{Книга|автор=Эрик Фримен, Элизабет Фримен|заглавие=Паттерны проектирования = Head First Desing Patterns|ответственный=|издание=|место=СПб|издательство=Питер|год=|страницы=|страниц=656|isbn=978-5-459-00435-9}} |
||
== Примечания == |
|||
{{примечания}} |
|||
== Ссылки == |
== Ссылки == |
||
* [http://cpp-reference.ru/patterns/structural-patterns/decorator/ Паттерн Decorator (Декоратор)] — назначение, описание, особенности и реализация на |
* [http://cpp-reference.ru/patterns/structural-patterns/decorator/ Паттерн Decorator (Декоратор)] — назначение, описание, особенности и реализация на C++. |
||
{{Типы шаблонов проектирования}} |
{{Типы шаблонов проектирования}} |
||
Строка 1032: | Строка 1293: | ||
[[Категория:Шаблоны проектирования]] |
[[Категория:Шаблоны проектирования]] |
||
[[Категория:Структурные шаблоны проектирования]] |
[[Категория:Структурные шаблоны проектирования]] |
||
[[Категория:Статьи с примерами кода Ruby]] |
Текущая версия от 14:03, 13 февраля 2024
Декоратор | |
---|---|
Decorator | |
Тип | структурный |
Назначение | для динамического подключения к объекту дополнительных обязательств |
Плюсы |
|
Родственные шаблоны | Фасад, Адаптер |
Описан в Design Patterns | Да |
Декоратор (англ. Decorator) — структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности.
Основные характеристики
[править | править код]Задача
[править | править код]Объект, который предполагается использовать, выполняет основные функции. Однако может потребоваться добавить к нему некоторую дополнительную функциональность, которая будет выполняться до, после или даже вместо основной функциональности объекта.
Способ решения
[править | править код]Декоратор предусматривает расширение функциональности объекта без определения подклассов.
Участники
[править | править код]Класс ConcreteComponent
— класс, в который с помощью шаблона Декоратор добавляется новая функциональность. В некоторых случаях базовая функциональность предоставляется классами, производными от класса ConcreteComponent
. В подобных случаях класс ConcreteComponent
является уже не конкретным, а абстрактным. Абстрактный класс Component
определяет интерфейс для использования всех этих классов.
Следствия
[править | править код]- Добавляемая функциональность реализуется в небольших объектах. Преимущество состоит в возможности динамически добавлять эту функциональность до или после основной функциональности объекта
ConcreteComponent
. - Позволяет избегать перегрузки функциональными классами на верхних уровнях иерархии
- Декоратор и его компоненты не являются идентичными
Реализация
[править | править код]Создаётся абстрактный класс, представляющий как исходный класс, так и новые, добавляемые в класс функции. В классах-декораторах новые функции вызываются в требуемой последовательности — до или после вызова последующего объекта.
При желании остаётся возможность использовать исходный класс (без расширения функциональности), если на его объект сохранилась ссылка.
Замечания и комментарии
[править | править код]- Хотя объект-декоратор может добавлять свою функциональность до или после функциональности основного объекта, цепочка создаваемых объектов всегда должна заканчиваться объектом класса
ConcreteComponent
. - Базовые классы языка Java широко используют шаблон Декоратор для организации обработки операций ввода-вывода.
- И декоратор, и адаптер являются обёртками вокруг объекта — хранят в себе ссылку на оборачиваемый объект и часто передают в него вызовы методов. Отличие декоратора от адаптера в том, что адаптер имеет внешний интерфейс, отличный от интерфейса оборачиваемого объекта, и используется именно для стыковки разных интерфейсов. Декоратор же имеет точно такой же интерфейс, и используется для добавления функциональности.
- Для расширения функциональности класса возможно использовать как декораторы, так и стратегии. Декораторы оборачивают объект снаружи, стратегии же вставляются в него внутрь по неким интерфейсам.
- Недостаток стратегии: класс должен быть спроектирован с возможностью вставления стратегий, декоратор же не требует такой поддержки.
- Недостаток декоратора: он оборачивает ровно тот же интерфейс, что предназначен для внешнего мира, что вызывает смешение публичного интерфейса и интерфейса кастомизации, которое не всегда желательно.
Применение шаблона
[править | править код]Драйверы-фильтры в ядре Windows (архитектура WDM (Windows Driver Model)) представляют собой декораторы. Несмотря на то, что WDM реализована на не-объектном языке Си, в ней чётко прослеживаются паттерны проектирования — декоратор, цепочка обязанностей, и команда (объект IRP).
Архитектура COM (Component Object Model) не поддерживает наследование реализаций, вместо него предлагается использовать декораторы (в данной архитектуре это называется «агрегация»). При этом архитектура решает (с помощью механизма pUnkOuter) проблему object identity, возникающую при использовании декораторов — identity агрегата есть identity его самого внешнего декоратора.
Примеры
[править | править код]Kotlin
[править | править код]fun main() {
LoggingNotifier(
FancyNotifier(
ConsoleNotifier()
)
).notify("Hello, World!")
}
interface Notifier {
fun notify(message: String)
}
class ConsoleNotifier : Notifier {
override fun notify(message: String) {
println(message)
}
}
class LoggingNotifier(private val notifier: Notifier) : Notifier {
override fun notify(message: String) {
notifier.notify(message)
println("LOG - $message") // Like a logger
}
}
class FancyNotifier(private val notifier: Notifier) : Notifier {
override fun notify(message: String) {
val border = "-".repeat(message.length)
notifier.notify("""
$border
$message
$border
""".trimIndent())
}
}
Ruby
[править | править код]module DecoratorPattern
# Extends basic functionallity combining several Decorators
class Source
def initialize(line)
@line = line
end
def write_line
@line
end
end
# Abstract Decorator
module Decorator
def initialize(source)
@source = source
end
def write_line
raise NotImplementedError
end
end
# Concrete Decorator
class Upcaser
include Decorator
def write_line
@source.write_line.upcase
end
end
# Concrete Decorator
class Timestamper
include Decorator
def write_line
"#{Time.now.strftime('%H:%m')} #{@source.write_line}"
end
end
# Concrete Decorator
class Datestamper
include Decorator
def write_line
"#{Time.now.strftime('%d.%m.%y')} #{@source.write_line}"
end
end
def self.run
puts '=> Decorator'
source = Source.new('Lorem ipsum dolor sit amet')
puts "Source:\n=> #{source.write_line}"
upcased = Upcaser.new(source)
puts "Upcased:\n=> #{upcased.write_line}"
timestamped = Timestamper.new(source)
puts "Timestamped:\n=> #{timestamped.write_line}"
datestamped = Datestamper.new(source)
puts "Datestamped:\n=> #{datestamped.write_line}"
upcased_timestamped = Timestamper.new(Upcaser.new(source))
puts "Upcased and timestamped:\n=> #{upcased_timestamped.write_line}"
upcased_datestamped_timestamped = Datestamper.new(Timestamper.new(Upcaser.new(source)))
puts "Upcased, datestamped and timestamped:\n=> #{upcased_datestamped_timestamped.write_line}"
datestamped_timestamped = Datestamper.new(Timestamper.new(source))
puts "Datestamped and timestamped:\n=> #{datestamped_timestamped.write_line}"
puts ''
end
end
DecoratorPattern.run
# => Decorator
# Source:
# => Lorem ipsum dolor sit amet
# Upcased:
# => LOREM IPSUM DOLOR SIT AMET
# Timestamped:
# => 18:03 Lorem ipsum dolor sit amet
# Datestamped:
# => 29.03.19 Lorem ipsum dolor sit amet
# Upcased and timestamped:
# => 18:03 LOREM IPSUM DOLOR SIT AMET
# Upcased, datestamped and timestamped:
# => 29.03.19 18:03 LOREM IPSUM DOLOR SIT AMET
# Datestamped and timestamped:
# => 29.03.19 18:03 Lorem ipsum dolor sit amet
Java
[править | править код]public interface InterfaceComponent {
void doOperation();
}
class MainComponent implements InterfaceComponent {
@Override
public void doOperation() {
System.out.print("World!");
}
}
abstract class Decorator implements InterfaceComponent {
protected InterfaceComponent component;
public Decorator (InterfaceComponent c) {
component = c;
}
@Override
public void doOperation() {
component.doOperation();
}
public void newOperation() {
System.out.println("Do Nothing");
}
}
class DecoratorSpace extends Decorator {
public DecoratorSpace(InterfaceComponent c) {
super(c);
}
@Override
public void doOperation() {
System.out.print(" ");
super.doOperation();
}
@Override
public void newOperation() {
System.out.println("New space operation");
}
}
class DecoratorComma extends Decorator {
public DecoratorComma(InterfaceComponent c) {
super(c);
}
@Override
public void doOperation() {
System.out.print(",");
super.doOperation();
}
@Override
public void newOperation() {
System.out.println("New comma operation");
}
}
class DecoratorHello extends Decorator {
public DecoratorHello(InterfaceComponent c) {
super(c);
}
@Override
public void doOperation() {
System.out.print("Hello");
super.doOperation();
}
@Override
public void newOperation() {
System.out.println("New hello operation");
}
}
class Main {
public static void main (String... s) {
Decorator c = new DecoratorHello(new DecoratorComma(new DecoratorSpace(new MainComponent())));
c.doOperation(); // Результат выполнения программы "Hello, World!"
c.newOperation(); // New hello operation
}
}
C#
[править | править код]using System;
namespace Decorator
{
class MainApp
{
static void Main()
{
// Create ConcreteComponent and two Decorators
ConcreteComponent c = new ConcreteComponent();
ConcreteDecoratorA dA = new ConcreteDecoratorA();
ConcreteDecoratorB dB = new ConcreteDecoratorB();
// Link decorators
dA.SetComponent(c);
dB.SetComponent(dA);
dA.Operation();
Console.WriteLine();
dB.Operation();
// Wait for user
Console.Read();
}
}
/// <summary>
/// Component - компонент
/// </summary>
/// <remarks>
/// <li>
/// <lu>определяем интерфейс для объектов, на которые могут быть динамически
/// возложены дополнительные обязанности;</lu>
/// </li>
/// </remarks>
abstract class Component
{
public abstract void Operation();
}
/// <summary>
/// ConcreteComponent - конкретный компонент
/// </summary>
/// <remarks>
/// <li>
/// <lu>определяет объект, на который возлагается дополнительные обязанности</lu>
/// </li>
/// </remarks>
class ConcreteComponent : Component
{
public override void Operation()
{
Console.Write("Привет");
}
}
/// <summary>
/// Decorator - декоратор
/// </summary>
/// <remarks>
/// <li>
/// <lu>хранит ссылку на объект <see cref="Component"/> и определяет интерфейс,
/// соответствующий интерфейсу <see cref="Component"/></lu>
/// </li>
/// </remarks>
abstract class Decorator : Component
{
protected Component component;
public void SetComponent(Component component)
{
this.component = component;
}
public override void Operation()
{
if (component != null)
{
component.Operation();
}
}
}
/// <summary>
/// ConcreteDecoratorA - конкретный декоратор
/// </summary>
/// <remarks>
/// <li>
/// <lu>Выполняет основную задачу</lu>
/// </li>
/// </remarks>
class ConcreteDecoratorA : Decorator
{
public override void Operation()
{
base.Operation();
}
}
/// <summary>
/// ConcreteDecorator - конкретный декоратор
/// </summary>
/// <remarks>
/// <li>
/// <lu>Выполняет основную задачу + дополнительную</lu>
/// </li>
/// </remarks>
class ConcreteDecoratorB : Decorator
{
public override void Operation()
{
base.Operation();
Console.Write(" Мир!");
}
}
}
C++
[править | править код]#include <iostream>
#include <memory>
class IComponent {
public:
virtual void operation() = 0;
virtual ~IComponent(){}
};
class Component : public IComponent {
public:
virtual void operation() {
std::cout<<"World!"<<std::endl;
}
};
class DecoratorOne : public IComponent {
std::shared_ptr<IComponent> m_component;
public:
DecoratorOne(std::shared_ptr<IComponent> component): m_component(component) {}
virtual void operation() {
std::cout << ", ";
m_component->operation();
}
};
class DecoratorTwo : public IComponent {
std::shared_ptr<IComponent> m_component;
public:
DecoratorTwo(std::shared_ptr<IComponent> component): m_component(component) {}
virtual void operation() {
std::cout << "Hello";
m_component->operation();
}
};
void Operation(IComponent &component) {
component.operation();
}
int main() {
DecoratorTwo obj(std::make_shared<DecoratorOne>(std::make_shared<Component>()));
Operation(obj); // prints "Hello, World!\n"
return 0;
}
D
[править | править код]import std.stdio;
abstract class Figure
{
protected string name;
string getInfo();
}
class Empty : Figure
{
override string getInfo()
{
return null;
}
}
class Circle : Figure
{
protected Figure figure;
this(Figure f)
{
figure = f;
name = " circle ";
}
override string getInfo()
{
return name ~ figure.getInfo();
}
}
class Bar : Figure
{
protected Figure figure;
this(Figure f)
{
figure = f;
name = " bar ";
}
override string getInfo()
{
return figure.getInfo() ~ name;
}
}
void main()
{
Figure figures = new Bar(new Circle(new Bar(new Circle(new Empty()))));
writeln(figures.getInfo());
}
Python
[править | править код]Ниже — пример реализации шаблона проектирования. В Python существуют декораторы функций и классов, концепция которых отличается от концепции шаблона проектирования.
"""
Demonstrated decorators in a world of a 10x10 grid of values 0-255.
"""
import random
def s32_to_u16( x ):
if x < 0:
sign = 0xf000
else:
sign = 0
bottom = x & 0x00007fff
return bottom | sign
def seed_from_xy( x,y ): return s32_to_u16( x ) | (s32_to_u16( y ) << 16 )
class RandomSquare:
def __init__( s, seed_modifier ):
s.seed_modifier = seed_modifier
def get( s, x,y ):
seed = seed_from_xy( x,y ) ^ s.seed_modifier
random.seed( seed )
return random.randint( 0,255 )
class DataSquare:
def __init__( s, initial_value = None ):
s.data = [initial_value]*10*10
def get( s, x,y ):
return s.data[ (y*10)+x ] # yes: these are all 10x10
def set( s, x,y, u ):
s.data[ (y*10)+x ] = u
class CacheDecorator:
def __init__( s, decorated ):
s.decorated = decorated
s.cache = DataSquare()
def get( s, x,y ):
if s.cache.get( x,y ) == None:
s.cache.set( x,y, s.decorated.get( x,y ) )
return s.cache.get( x,y )
class MaxDecorator:
def __init__( s, decorated, max ):
s.decorated = decorated
s.max = max
def get( s, x,y ):
if s.decorated.get( x,y ) > s.max:
return s.max
return s.decorated.get( x,y )
class MinDecorator:
def __init__( s, decorated, min ):
s.decorated = decorated
s.min = min
def get( s, x,y ):
if s.decorated.get( x,y ) < s.min:
return s.min
return s.decorated.get( x,y )
class VisibilityDecorator:
def __init__( s, decorated ):
s.decorated = decorated
def get( s,x,y ):
return s.decorated.get( x,y )
def draw(s ):
for y in range( 10 ):
for x in range( 10 ):
print "%3d" % s.get( x,y ),
print
# Now, build up a pipeline of decorators:
random_square = RandomSquare( 635 )
random_cache = CacheDecorator( random_square )
max_filtered = MaxDecorator( random_cache, 200 )
min_filtered = MinDecorator( max_filtered, 100 )
final = VisibilityDecorator( min_filtered )
final.draw()
Выходные данные (учтите использование генератора псевдослучайных чисел):
100 100 100 100 181 161 125 100 200 100
200 100 100 200 100 200 200 184 162 100
155 100 200 100 200 200 100 200 143 100
100 200 144 200 101 143 114 200 166 136
100 147 200 200 100 100 200 141 172 100
144 161 100 200 200 200 190 125 100 177
150 200 100 175 111 195 193 128 100 100
100 200 100 200 200 129 159 105 112 100
100 101 200 200 100 100 200 100 101 120
180 200 100 100 198 151 100 195 131 100
PHP
[править | править код]abstract class AbstractComponent
{
abstract public function operation();
}
class ConcreteComponent extends AbstractComponent
{
public function operation()
{
// ...
}
}
abstract class AbstractDecorator extends AbstractComponent
{
protected $component;
public function __construct(AbstractComponent $component)
{
$this->component = $component;
}
}
class ConcreteDecorator extends AbstractDecorator
{
public function operation()
{
// ... расширенная функциональность ...
$this->component->operation();
// ... расширенная функциональность ...
}
}
$decoratedComponent = new ConcreteDecorator(
new ConcreteComponent()
);
$decoratedComponent->operation();
PHP 5
[править | править код]<?php
interface IText
{
public function show();
}
class TextHello implements IText
{
protected $object;
public function __construct(IText $text) {
$this->object = $text;
}
public function show() {
echo 'Hello';
$this->object->show();
}
}
class TextWorld implements IText
{
protected $object;
public function __construct(IText $text) {
$this->object = $text;
}
public function show() {
echo 'world';
$this->object->show();
}
}
class TextSpace implements IText
{
protected $object;
public function __construct(IText $text) {
$this->object = $text;
}
public function show() {
echo ' ';
$this->object->show();
}
}
class TextEmpty implements IText
{
public function show() {
}
}
$decorator = new TextHello(new TextSpace(new TextWorld(new TextEmpty())));
$decorator->show(); // Hello world
echo '<br />' . PHP_EOL;
$decorator = new TextWorld(new TextSpace(new TextHello(new TextEmpty())));
$decorator->show(); // world Hello
CoffeeScript
[править | править код]# Компонент
class Notebook
# Маркетинг
price : 500 # $
# Характеристики
hdd : 320 # GB
ram : 4 # GB
core : 'i5 2.3' # GHz
# Декоратор
class NovaNotebook
constructor : (product) ->
@price = product.price * 1.3
# Декоратор
class ImportNotebook
constructor : (product) ->
@price = product.price * 1.5
# Декоратор
class AppleNotebook
constructor : (product) ->
@price = product.price * 2.1
macBookInRussia = new ImportNotebook new NovaNotebook new AppleNotebook new Notebook
console.log(macBookInRussia.price)
JavaScript
[править | править код]Шаблон декоратор в языках с динамической типизацией может быть применён без интерфейсов и традиционного для ООП наследования.
Этот пример скопирован с английской версии статьи. Расчёт стоимости кофе:
// ConcreteComponent (класс для последующего декорирования)
function Coffee() {
this.cost = function() {
return 1;
};
}
// Decorator A
function Milk(coffee) {
this.cost = function() {
return coffee.cost() + 0.5;
};
}
// Decorator B
function Whip(coffee) {
this.cost = function() {
return coffee.cost() + 0.7;
};
}
// Decorator C
function Sprinkles(coffee) {
this.cost = function() {
return coffee.cost() + 0.2;
};
}
// Можно использовать, например, так:
var coffee = new Milk(new Whip(new Sprinkles(new Coffee())));
alert( coffee.cost() );
// Или более наглядно:
var coffee = new Coffee();
coffee = new Sprinkles(coffee);
coffee = new Whip(coffee);
coffee = new Milk(coffee);
alert(coffee.cost());
Реализация имеющегося выше C# примера. В ConcreteComponent добавлена локальная переменная price, которая будет изменяться как в нём самом, так и декораторах. Имена классов (кроме постфиксов "A" и "B") совпадают с именами участников шаблона.
function Component() {
this.operation = function() { };
this.getPrice = function() { };
this.setPrice = function() { };
}
function ConcreteComponent() {
var price = 10;
this.operation = function() {
price += 4;
alert("ConcreteComponent.operation, price: "+ price);
};
this.getPrice = function() {
return price;
};
this.setPrice = function(val) {
price = val;
};
}
ConcreteComponent.prototype = new Component();
ConcreteComponent.prototype.constructor = ConcreteComponent;
function Decorator() {
var component;
this.setComponent = function(val) {
component = val;
};
this.getComponent = function() {
return component;
};
this.operation = function() {
component.operation();
};
this.getPrice = function() {
return component.getPrice();
};
this.setPrice = function(val) {
component.setPrice(val);
};
}
Decorator.prototype = new Component();
Decorator.prototype.constructor = Decorator;
function ConcreteDecoratorA() {
Decorator.call(this);
var operation = this.operation; // ссылка на метод, определенный в Decorator
this.operation = function() {
this.setPrice(this.getPrice() + 3);
alert("ConcreteDecoratorA.operation, price: "+ this.getPrice());
operation();
};
}
function ConcreteDecoratorB() {
var dublicate = this; // ссылка на инстанцирующийся объект (т.к. this может меняться)
Decorator.call(this);
var operation = this.operation; // ссылка на метод, определенный в Decorator
this.operation = function() {
this.setPrice(this.getPrice() + 1);
alert("ConcreteDecoratorB.operation, price: "+ this.getPrice());
addedBehavior();
operation();
};
function addedBehavior() {
dublicate.setPrice(dublicate.getPrice() + 2);
alert("addedBehavior, price: "+ dublicate.getPrice());
}
}
// использование
c = new ConcreteComponent();
d1 = new ConcreteDecoratorA();
d2 = new ConcreteDecoratorB();
alert("изначальная цена: " + c.getPrice()); // 10
d1.setComponent(c);
d2.setComponent(d1);
d2.operation();
alert("цена после преобразования: " + c.getPrice()); // 20
VB.NET
[править | править код]Namespace Decorator
Class Program
Shared Sub Main()
' Создание ConcreteComponent и двух декораторов
Dim C As New ConcreteComponent()
Dim D1 As New ConcreteDecoratorA()
Dim D2 As New ConcreteDecoratorB()
' Ссылки декоратора
D1.SetComponent(C)
D2.SetComponent(D1)
D2.Operation()
' Ожидание действий от пользователя
Console.Read()
End Sub
End Class
''' <summary>
''' Component - компонент
''' </summary>
''' <remarks>
''' <li>
''' <lu>определяем интерфейс для объектов, на которые могут быть динамически
''' возложены дополнительные обязанности;</lu>
''' </li>
''' </remarks>
MustInherit Class Component
Public MustOverride Sub Operation()
End Class
''' <summary>
''' ConcreteComponent - конкретный компонент
''' </summary>
''' <remarks>
''' <li>
''' <lu>определяет объект, на который возлагается дополнительные обязанности</lu>
''' </li>
''' </remarks>
Class ConcreteComponent
Inherits Component
Public Overrides Sub Operation()
Console.WriteLine("ConcreteComponent.Operation()")
End Sub
End Class
''' <summary>
''' Decorator - декоратор
''' </summary>
''' <remarks>
''' <li>
''' <lu>хранит ссылку на объект <see cref="Component"/> и определяет интерфейс,
''' соответствующий интерфейсу <see cref="Component"/></lu>
''' </li>
''' </remarks>
MustInherit Class Decorator
Inherits Component
Protected component As Component
Public Sub SetComponent(ByVal component As Component)
Me.component = component
End Sub
Public Overrides Sub Operation()
If component IsNot Nothing Then
component.Operation()
End If
End Sub
End Class
''' <summary>
''' ConcreteDecorator - конкретный декоратор
''' </summary>
''' <remarks>
''' <li>
''' <lu>возглагает дополнительные обязанности на компонент.</lu>
''' </li>
''' </remarks>
Class ConcreteDecoratorA
Inherits Decorator
Private addedState As String
Public Overrides Sub Operation()
MyBase.Operation()
addedState = "New State"
Console.WriteLine("ConcreteDecoratorA.Operation()")
End Sub
End Class
' "ConcreteDecoratorB"
Class ConcreteDecoratorB
Inherits Decorator
Public Overrides Sub Operation()
MyBase.Operation()
AddedBehavior()
Console.WriteLine("ConcreteDecoratorB.Operation()")
End Sub
Private Sub AddedBehavior()
End Sub
End Class
End Namespace
Delphi
[править | править код]Языки Delphi и Free Pascal поддерживают class helpers, которые делают ненужным использование шаблона декоратор.
program NoMoreDecorators;
type
TMyObject = class
procedure WriteHello;
end;
TMyObjectHelper = class helper for TMyObject
procedure WriteHello(const Name: string); overload;
end;
procedure TMyObject.WriteHello;
begin
writeln('Hello');
end;
procedure TMyObjectHelper.WriteHello(const Name: string);
begin
writeln('Hello, ', Name, '!');
end;
var
o: TMyObject;
begin
o := TMyObject.Create;
o.WriteHello;
o.WriteHello('Jean');
o.Free;
end.
program DecoratorPattern;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TInterfaceComponent = class
public
procedure Operation; virtual; abstract;
end;
type
TConcreteComponent = class(TInterfaceComponent)
public
procedure Operation; override;
end;
procedure TConcreteComponent.Operation;
begin
Write('нельзя');
end;
type
TDecorator = class(TInterfaceComponent)
private
FComponent: TInterfaceComponent;
public
constructor Create(aComponent: TInterfaceComponent);
end;
constructor TDecorator.Create(aComponent: TInterfaceComponent);
begin
FComponent := aComponent;
end;
type
TBeforeDecorator = class(TDecorator)
public
procedure Operation; override;
end;
procedure TBeforeDecorator.Operation;
begin
Write('Казнить, ');
FComponent.Operation;
end;
type
TAfterDecorator = class(TDecorator)
public
procedure Operation; override;
end;
procedure TAfterDecorator.Operation;
begin
FComponent.Operation;
Write(' помиловать');
end;
type
TOverrideDecorator = class(TDecorator)
public
procedure Operation; override;
end;
procedure TOverrideDecorator.Operation;
begin
Write('Любите друг друга!');
end;
var
vSameComponent: TInterfaceComponent;
begin
vSameComponent := TAfterDecorator.Create(TConcreteComponent.Create);
vSameComponent.Operation; // Будет выведено "нельзя помиловать"
Writeln;
vSameComponent := TBeforeDecorator.Create(vSameComponent);
vSameComponent.Operation; // Будет выведено "Казнить, нельзя помиловать"
Writeln;
vSameComponent := TOverrideDecorator.Create(vSameComponent);
vSameComponent.Operation; // Будет выведено "Любите друг друга!"
// Ради упрощения примера уничтожение объектов не показано
ReadLn;
end.
Swift
[править | править код]protocol Book {
var title: String { get set }
var price: Int { get set }
func getPrice() -> Int
}
class BookImpl: Book {
var title: String = ""
var price: Int = 1000
func getPrice() -> Int {
return price
}
}
class DiscountBook: Book {
let element: BookImpl
var title: String = "«Грокаем алгоритмы»"
var price: Int = 0
init(element: BookImpl) {
self.element = element
self.title = element.title
self.price = element.price
}
// 30% sale
func getPrice() -> Int {
return price - (price * 30)/100
}
}
// Use Decorator
let book = BookImpl()
let discountBook = DiscountBook(element: book)
print(discountBook.getPrice())
Литература
[править | править код]- Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М.: «Вильямс», 2002. — С. 288. — ISBN 0-201-71594-5.
- Эрик Фримен, Элизабет Фримен. Паттерны проектирования = Head First Desing Patterns. — СПб.: Питер. — 656 с. — ISBN 978-5-459-00435-9.
Примечания
[править | править код]- ↑ Decorator Pattern . wiki.python.org. Дата обращения: 24 октября 2021. Архивировано 24 октября 2021 года.
Ссылки
[править | править код]- Паттерн Decorator (Декоратор) — назначение, описание, особенности и реализация на C++.