Шаблон декоратора

редактировать

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

Содержание
  • 1 Обзор
    • 1.1 Какие проблемы он может решить?
    • 1.2 Какое решение он описывает?
  • 2 Намерение
  • 3 Мотивация
  • 4 Использование
  • 5 Структура
    • 5.1 Класс UML и диаграмма последовательности
  • 6 Примеры
    • 6.1 Go
    • 6.2 C ++
      • 6.2.1 Динамический декоратор
      • 6.2.2 Статический декоратор (наследование Mixin)
    • 6.3 Java
      • 6.3.1 Первый пример (сценарий окна / прокрутки)
      • 6.3.2 Второй пример (сценарий приготовления кофе)
    • 6.4 PHP
    • 6.5 Python
    • 6.6 Crystal
    • 6.7 C #
  • 7 См. также
  • 8 Ссылки
  • 9 Внешние ссылки
Обзор

Шаблон проектирования декоратора - один из двадцати трех хорошо известных шаблонов проектирования GoF ; они описывают, как решать повторяющиеся проблемы проектирования и разрабатывать гибкое и многократно используемое объектно-ориентированное программное обеспечение, то есть объекты, которые легче реализовать, изменить, протестировать и повторно использовать.

Какие проблемы это может решить?

  • Обязанности должны добавляться (и удаляться) к объекту динамически во время выполнения.
  • Должна быть предусмотрена гибкая альтернатива подклассу для расширения функциональности.

При использовании подкласса различные подклассы расширяют класс по-разному. Но расширение привязывается к классу во время компиляции и не может быть изменено во время выполнения.

Какое здесь решение?

Определите объекты Decorator, которые

  • прозрачно реализуют интерфейс расширенного (декорированного) объекта (Component), перенаправляя на него все запросы
  • выполнять дополнительные функции до / после пересылки запроса.

Это позволяет работать с различными объектами Decoratorдля динамического расширения функциональности объекта во время выполнения.. См. Также схему классов и последовательности UML ниже.

Intent
Decorator UML диаграмма классов

Шаблон декоратора может использоваться для расширения (украшения) функциональности определенного объекта статически или в некоторых случаях в среда выполнения, независимо от других экземпляров того же класса , при условии, что во время разработки выполняется некоторая подготовка. Это достигается путем разработки нового класса Decorator, который обертывает исходный класс. Эта упаковка может быть достигнута с помощью следующей последовательности шагов:

  1. Подкласс исходного класса Component в класс Decorator (см. Диаграмму UML);
  2. В классе Decorator добавьте указатель Component в качестве поля;
  3. В классе Decorator передайте компонент в конструктор Decorator для инициализации указателя Component;
  4. в классе Decorator перенаправьте все методы компонента на указатель Component; и
  5. В классе ConcreteDecorator переопределите любые методы Component, поведение которых необходимо изменить.

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

Обратите внимание, что декораторы и исходный объект класса имеют общий набор функций. На предыдущей диаграмме метод operation () был доступен как в декорированной, так и в недекорированной версиях.

Особенности оформления (например, методы, свойства или другие элементы) обычно определяются интерфейсом, миксином (также известным как признак ) или наследованием классов, которое является общим декораторами и декорируемым объектом. В предыдущем примере класс Component наследуется как ConcreteComponent, так и подклассами, которые наследуются от Decorator.

Шаблон декоратора является альтернативой подклассу. Создание подклассов добавляет поведение во время компиляции, и изменение затрагивает все экземпляры исходного класса; декорирование может обеспечить новое поведение в времени выполнения для выбранных объектов.

Это различие становится наиболее важным, когда есть несколько независимых способов расширения функциональности. В некоторых объектно-ориентированных языках программирования классы не могут быть созданы во время выполнения, и, как правило, во время разработки невозможно предсказать, какие комбинации расширений потребуются. Это означало бы, что нужно было бы создать новый класс для каждой возможной комбинации. Напротив, декораторы - это объекты, созданные во время выполнения, и их можно комбинировать для каждого использования. Реализации потоков ввода-вывода как Java, так и .NET Framework включают шаблон декоратора.

Мотивация
UML-диаграмма для примера окна

В качестве примера рассмотрим окно в оконной системе. Чтобы разрешить прокрутку содержимого окна, можно добавить к нему горизонтальные или вертикальные полосы прокрутки, в зависимости от ситуации. Предположим, что окна представлены экземплярами интерфейса Window, и предположим, что этот класс не имеет функции для добавления полос прокрутки. Можно создать подкласс ScrollingWindow, который их предоставляет, или создать ScrollingWindowDecorator, который добавляет эту функциональность к существующим объектам Window. На этом этапе подойдет любое решение.

Теперь предположим, что кому-то также нужна возможность добавлять границы к окнам. Опять же, исходный класс Window не имеет поддержки. Подкласс ScrollingWindow теперь представляет проблему, потому что он фактически создал новый вид окна. Если кто-то хочет добавить поддержку границ для многих, но не для всех окон, необходимо создать подклассы WindowWithBorder и ScrollingWindowWithBorder и т. Д. Эта проблема усугубляется с каждым добавлением новой функции или подтипа окна. Для решения декоратора создается новый BorderedWindowDecorator. Любая комбинация ScrollingWindowDecorator или BorderedWindowDecorator может украсить существующие окна. Если функциональность должна быть добавлена ​​ко всем Windows, базовый класс можно изменить. С другой стороны, иногда (например, с использованием внешних фреймворков) изменение базового класса невозможно, законно или удобно.

В предыдущем примере классы SimpleWindow и WindowDecorator реализуют интерфейс Window, который определяет метод draw () и метод getDescription (), которые требуются в этом сценарии для украшения элемента управления окном.

Использование

Декоратор позволяет добавлять или изменять поведение интерфейса во время выполнения. В качестве альтернативы можно использовать адаптер, когда оболочка должна учитывать конкретный интерфейс и поддерживать полиморфное поведение, а Фасад, когда более простой или более простой интерфейс для желателен базовый объект.

ШаблонНамерение
Адаптер Преобразует один интерфейс в другой так, чтобы он соответствовал ожиданиям клиента
ДекораторДинамически добавляет ответственности к интерфейсу путем обертывания исходного кода
Фасад Обеспечивает упрощенный интерфейс
Структура

Класс UML и диаграмма последовательности

Образец класса UML и диаграмма последовательности для шаблона проектирования Decorator.

В приведенной выше диаграмме классов UML абстрактный класс Decoratorподдерживает ссылку (компонент) на декорированный объект (Component) и пересылает ему все запросы (component.operation ()). Это делает Decoratorпрозрачным (невидимым) для клиентов подклассов Component.

(Decorator1, Decorator2), реализующих дополнительное поведение (addBehavior ()), который должен быть добавлен к Компоненту(до / после пересылки ему запроса).. На диаграмме последовательности показаны взаимодействия во время выполнения: объект Clientработает через объекты Decorator1и Decorator2, чтобы расширить функциональность Component1объект.. Клиентвызывает operation ()для Decorator1, который перенаправляет запрос в Decorator2. Decorator2выполняет addBehavior ()после перенаправления запроса в Component1и возвращается в Decorator1, который выполняет addBehavior ()и возвращается к клиенту.

Примеры

Go

package decolog import ("log" "time") // OperateFn представляет операции, требующие типа оформления OperateFn func () // Decorate операция func Decorate (opFn OperateFn) {defer func (s time.Time) {log.Printf ("прошедшее время% 0,2d мс", time.Since (s).Nanoseconds () / (1 <<20)) }(time.Now()) // real operation function opFn() } // package main package main import ( "github.com/tkstorm/go-design/structural/decorator/decolog" "log" "math/rand" "time") //output: //2019/08/19 19:05:24 finish action a //2019/08/19 19:05:24 elapsed time 77 ms //2019/08/19 19:05:24 finish action b //2019/08/19 19:05:24 elapsed time 88 ms func main() { // decorate log a decolog.Decorate(decolog.OperateFn(DoActionA)) // decorate log b decolog.Decorate(decolog.OperateFn(DoActionB)) } func DoActionA() { time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond) log.Println("finish action a") } func DoActionB() { time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond) log.Println("finish action b") }

C ++

Здесь представлены два варианта: первый - динамический, составляемый во время выполнения декоратор (имеет проблемы с вызовом декорированных функций, если явно не проксируется) и декоратор, который использует наследование миксинов.

Dynamic Decorator

#include #include struct Shape {virtual ~ Shape () = default; virtual std :: string GetName () const = 0;}; struct Circle: Shape {void Resize (float factor) {radius * = factor; } std :: string GetName () const override {return std :: string ("A круг радиуса ") + std :: to_string (радиус); } радиус поплавка = 10.0f; }; struct ColoredShape: Shape {ColoredShape (const std :: string color, Shape * shape): color (цвет), shape (форма) {} std :: string GetName () const override {return shape->GetName () + "который окрашен "+ цвет; } std :: string color; Форма * форма; }; int main () {Круг, круг; ColoredShape coloured_shape ("красный", круг); std :: cout << colored_shape.GetName() << std::endl; }
#include #include #include struct WebPage {virtual void display () = 0; виртуальный ~ WebPage () = по умолчанию; }; struct BasicWebPage: WebPage {std :: string html; void display () override {std :: cout << "Basic WEB page" << std::endl; } ~BasicWebPage()=default; }; struct WebPageDecorator : WebPage { WebPageDecorator(std::unique_ptrwebPage): _webPage (std :: move (webPage)) {} void display () override {_webPage->display (); } ~ WebPageDecorator () = по умолчанию; частный: std :: unique_ptr _webPage; }; struct AuthenticatedWebPage: WebPageDecorator {AuthenticatedWebPage (std :: unique_ptr webPage): WebPageDecorator (std :: move (webPage)) {} void authenticateUser () {std :: cout << "authentification done" << std::endl; } void display() override { authenticateUser(); WebPageDecorator::display(); } ~AuthenticatedWebPage()=default; }; struct AuthorizedWebPage : WebPageDecorator { AuthorizedWebPage(std::unique_ptrwebPage): WebPageDecorator (std :: move (webPage)) {} void authorizedUser () {std :: cout << "authorized done" << std::endl; } void display() override { authorizedUser(); WebPageDecorator::display(); } ~AuthorizedWebPage()=default; }; int main(int argc, char* argv) { std::unique_ptrmyPage = std :: make_unique (); myPage = std :: make_unique (std :: move (myPage)); myPage = std :: make_unique (std :: move (myPage)); myPage->display (); std :: cout << std::endl; return 0; }

Статический декоратор (наследование Mixin)

В этом примере демонстрируется реализация статического декоратора, которая возможна благодаря способности C ++ наследовать от аргумента шаблона.

#include #include struct Circle {void Resize (float factor) {radius * = factor; } std :: string GetName () const {return std :: string ("Круг радиуса") + std :: to_string (радиус); } радиус поплавка = 10.0f; }; template struct ColoredShape: public T {ColoredShape (const std :: string color): color (color) {} std :: string GetName () const {return T :: GetName () + "который окрашен" + цвет ; } std :: string color; }; int main () {ColoredShape красный_круг ("красный"); std :: cout << red_circle.GetName() << std::endl; red_circle.Resize(1.5f); std::cout << red_circle.GetName() << std::endl; }

Java

Первый пример (сценарий окна / прокрутки)

В следующем примере Java показано использование декораторов с использованием сценария окна / прокрутки.

// Открытый интерфейс класса оконного интерфейса Window {void draw (); // Рисует строку окна getDescription (); // Возвращает описание окна} // Реализация простого окна без полос прокрутки class SimpleWindow реализует Window {@Override public void draw () {// Draw window} @Override public String getDescription () {return "simple window" ; }}

Следующие классы содержат декораторы для всех классов Window, включая сами классы декораторов.

// класс абстрактного декоратора - обратите внимание, что он реализует оконный абстрактный класс WindowDecorator реализует Window {private final Window windowToBeDecorated; // Декорируемое Окно public WindowDecorator (Window windowToBeDecorated) {this.windowToBeDecorated = windowToBeDecorated; } @Override public void draw () {windowToBeDecorated.draw (); // Делегирование} @Override public String getDescription () {return windowToBeDecorated.getDescription (); // Делегирование}} // Первый конкретный декоратор, который добавляет функциональность вертикальной полосы прокрутки. Класс VerticalScrollBarDecorator расширяет WindowDecorator {public VerticalScrollBarDecorator (Window windowToBeDecorated) {super (windowToBeDecorated); } @Override public void draw () {super.draw (); drawVerticalScrollBar (); } private void drawVerticalScrollBar () {// Рисуем вертикальную полосу прокрутки} @Override public String getDescription () {return super.getDescription () + ", включая вертикальные полосы прокрутки"; }} // Второй конкретный декоратор, который добавляет функциональность горизонтальной полосы прокрутки. Класс HorizontalScrollBarDecorator расширяет WindowDecorator {public HorizontalScrollBarDecorator (Window windowToBeDecorated) {super (windowToBeDecorated); } @Override public void draw () {super.draw (); drawHorizontalScrollBar (); } private void drawHorizontalScrollBar () {// Рисуем горизонтальную полосу прокрутки} @Override public String getDescription () {return super.getDescription () + ", включая горизонтальные полосы прокрутки"; }}

Вот тестовая программа, которая создает экземпляр Window, который полностью декорирован (т.е. с вертикальными и горизонтальными полосами прокрутки), и печатает его описание:

public class DecoratedWindowTest {public static void main (String args) {// Создание декорированного окна с горизонтальной и вертикальной полосами прокрутки Window DecoratedWindow = new HorizontalScrollBarDecorator (new VerticalScrollBarDecorator (new SimpleWindow ())); // Выводим описание окна System.out.println (DecoratedWindow.getDescription ()); }}

Ниже представлен тестовый класс JUnit для разработки через тестирование

import static org.junit.Assert.assertEquals; import org.junit.Test; открытый класс WindowDecoratorTest {@Test public void testWindowDecoratorTest () {Window DecoratedWindow = new HorizontalScrollBarDecorator (новый VerticalScrollBarDecorator (новый SimpleWindow ())); // утверждаем, что описание действительно включает горизонтальные + вертикальные полосы прокрутки assertEquals ("простое окно, включая вертикальные полосы прокрутки, включая горизонтальные полосы прокрутки", decorWindow.getDescription ())}}

Результатом этой программы является "простое окно, включая вертикальные полосы прокрутки, включая горизонтальные полосы прокрутки ». Обратите внимание, как метод getDescriptionдвух декораторов сначала извлекает описание декорированного Windowи украшает его суффиксом.

Второй пример (сценарий приготовления кофе)

Следующий пример Java иллюстрирует использование декораторов в сценарии приготовления кофе. В этом примере сценарий включает только стоимость и ингредиенты.

// Интерфейс Coffee определяет функциональность Coffee, реализованную декоратором публичного интерфейса Coffee {public double getCost (); // Возвращает стоимость кофе public String getIngredients (); // Возвращает ингредиенты кофе} // Расширение простого кофе без каких-либо дополнительных ингредиентов public class SimpleCoffee реализует Coffee {@Override public double getCost () {return 1; } @Override public String getIngredients () {return "Coffee"; }}

Следующие классы содержат декораторы для всех классов Coffee, включая сами классы декораторов.

// Класс абстрактного декоратора - обратите внимание, что он реализует общедоступный абстрактный класс интерфейса Coffee. CoffeeDecorator реализует Coffee {private final Coffee DecoratedCoffee; общедоступный CoffeeDecorator (Кофе c) {this.decoratedCoffee = c; } @Override public double getCost () {// Реализация методов интерфейса return DecoratedCoffee.getCost (); } @Override public String getIngredients () {return DecoratedCoffee.getIngredients (); }} // Декоратор WithMilk подмешивает молоко в кофе. // Обратите внимание, что он расширяет CoffeeDecorator. class WithMilk расширяет CoffeeDecorator {общедоступное WithMilk (Кофе c) {super (c); } @Override public double getCost () {// Переопределение методов, определенных в абстрактном суперклассе return super.getCost () + 0.5; } @Override public String getIngredients () {return super.getIngredients () + ", Milk"; }} // Декоратор WithSprinkles смешивает кофейные посыпки. // Обратите внимание, что он расширяет CoffeeDecorator. class WithSprinkles расширяет CoffeeDecorator {public WithSprinkles (Coffee c) {super (c); } @Override public double getCost () {return super.getCost () + 0.2; } @Override public String getIngredients () {return super.getIngredients () + ", Sprinkles"; }}

Вот тестовая программа, которая создает экземпляр Coffee, который полностью украшен (с молоком и брызгами), и вычисляет стоимость кофе и печатает его ингредиенты:

public class Main {public static void printInfo ( Кофе c) {System.out.println ("Стоимость:" + c.getCost () + "; Ингредиенты:" + c.getIngredients ()); } public static void main (String args) {Coffee c = new SimpleCoffee (); printInfo (c); c = новое WithMilk (c); printInfo (c); c = новый WithSprinkles (c); printInfo (c); }}

Результат этой программы представлен ниже:

Стоимость: 1.0; Ингредиенты: Стоимость кофе: 1,5; Ингредиенты: кофе, молоко Стоимость: 1,7; Ингредиенты: кофе, молоко, крошка

PHP

абстрактный класс Component {protected $ data; защищенное значение $; абстрактная публичная функция getData (); абстрактная публичная функция getValue (); } class ConcreteComponent расширяет Component {public function __construct () {$ this->value = 1000; $ this->data = "Конкретный компонент: \ t {$ this->value} \ n"; } публичная функция getData () {return $ this->data; } публичная функция getValue () {return $ this->value; }} абстрактный класс Decorator расширяет компонент {} class ConcreteDecorator1 расширяет Decorator {публичная функция __construct (Component $ data) {$ this->value = 500; $ this->data = $ data; } публичная функция getData () {return $ this->data->getData (). "Конкретный декоратор 1: \ t {$ this->value} \ n"; } публичная функция getValue () {return $ this->value + $ this->data->getValue (); }} class ConcreteDecorator2 расширяет Decorator {публичная функция __construct (Component $ data) {$ this->value = 500; $ this->data = $ data; } публичная функция getData () {return $ this->data->getData (). "Конкретный декоратор 2: \ t {$ this->value} \ n"; } публичная функция getValue () {return $ this->value + $ this->data->getValue (); }} класс Client {private $ component; публичная функция __construct () {$ this->component = new ConcreteComponent (); $ this->component = $ this->wrapComponent ($ this->component); эхо $ this->component->getData (); echo "Клиент: \ t \ t \ t"; echo $ this->component->getValue (); } частная функция wrapComponent (Component $ component) {$ component1 = new ConcreteDecorator1 ($ component); $ component2 = новый ConcreteDecorator2 ($ component1); return $ component2; }} $ client = новый клиент (); // Результат: # Quanton81 // Конкретный компонент: 1000 // Конкретный декоратор 1: 500 // Конкретный декоратор 2: 500 // Клиент: 2000

Python

Следующий пример Python, взятый из Python Wiki - DecoratorPattern, показывает нам, как конвейерные декораторы для динамического добавления множества вариантов поведения в объект:

"" "Продемонстрированные декораторы в мире сетки 10x10 значений 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()

Примечание:

Пожалуйста, не путайте шаблон декоратора (или реализацию этого шаблона проектирования в Python - как в приведенном выше примере) с Python Decorators, функцией языка Python. Это разные вещи.

Второй после Python Wiki:

Шаблон декоратора - это шаблон, описанный в книге шаблонов проектирования. Это способ явно изменить поведение объекта, заключив его в украшающий объект с аналогичным интерфейсом. Это не следует путать с декораторами Python, которые представляют собой языковую функцию для динамического изменения функции или класса.

Crystal

абстрактный класс Coffee abstract def cost abstract def components end # Расширение простого класса кофе SimpleCoffee < Coffee def cost 1.0 end def ingredients "Coffee" end end # Abstract decorator class CoffeeDecorator < Coffee protected getter decorated_coffee : Coffee def initialize(@decorated_coffee) end def cost decorated_coffee.cost end def ingredients decorated_coffee.ingredients end end class WithMilk < CoffeeDecorator def cost super + 0.5 end def ingredients super + ", Milk" end end class WithSprinkles < CoffeeDecorator def cost super + 0.2 end def ingredients super + ", Sprinkles" end end class Program def print(coffee : Coffee) puts "Cost: #{coffee.cost}; Ingredients: #{coffee.ingredients}" end def initialize coffee = SimpleCoffee.new print(coffee) coffee = WithMilk.new(coffee) print(coffee) coffee = WithSprinkles.new(coffee) print(coffee) end end Program.new

Вывод:

Стоимость: 1.0; Ингредиенты: Стоимость кофе: 1,5; Ингредиенты: кофе, молоко Стоимость: 1,7; Ингредиенты: кофе, молоко, брызги

C #

пространство имен WikiDesignPatterns {общедоступный интерфейс IBike {string GetDetails (); двойной GetPrice (); } публичный класс AluminiumBike: IBike {публичный двойной GetPrice () {return 100; } публичная строка GetDetails () {return "Алюминиевый велосипед"; }} открытый класс CarbonBike: IBike {public double GetPrice () {return 1000; } публичная строка GetDetails () {return "Углерод"; }} открытый абстрактный класс BikeAccessories: IBike {частный только для чтения IBike _bike; общедоступные BikeAccessories (велосипед IBike) {_bike = bike; } публичный виртуальный двойной GetPrice () {return _bike.GetPrice (); } общедоступная виртуальная строка GetDetails () {return _bike.GetDetails (); }} открытый класс SecurityPackage: BikeAccessories {public SecurityPackage (IBike bike): base (bike) {} общедоступная строка переопределения GetDetails () {return base.GetDetails () + "+ Security Package"; } публичное переопределение двойной GetPrice () {return base.GetPrice () + 1; }} открытый класс SportPackage: BikeAccessories {public SportPackage (IBike bike): base (bike) {} общедоступная строка переопределения GetDetails () {return base.GetDetails () + "+ Sport Package"; } публичное переопределение двойной GetPrice () {return base.GetPrice () + 10; }} открытый класс BikeShop {public static void UpgradeBike () {var basicBike = new AluminiumBike (); Модернизированные аксессуары для велосипеда = новый спортивный пакет (базовый велосипед); обновлено = новый пакет безопасности (обновленный); Console.WriteLine ($ "Велосипед: '{обновлено.GetDetails ()}' Стоимость: {обновлено.GetPrice ()}"); }}}

Вывод:

Велосипед: «Алюминиевый велосипед + спортивный пакет + пакет безопасности» Стоимость: 111
См. Также
Ссылки
Внешние ссылки
Wikibook Computer Science Design Patterns имеет страницу тема: Реализации декоратора на разных языках
Последняя правка сделана 2021-05-17 10:57:35
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте