Я вивчав узор декораторів, як це зафіксовано в GOF .
Будь ласка, допоможіть мені зрозуміти шаблон декору . Чи може хтось навести приклад використання того, де це корисно в реальному світі?
Я вивчав узор декораторів, як це зафіксовано в GOF .
Будь ласка, допоможіть мені зрозуміти шаблон декору . Чи може хтось навести приклад використання того, де це корисно в реальному світі?
Відповіді:
Шаблон декоратора досягає єдиної мети - динамічно додавати обов'язки до будь-якого об’єкта.
Розглянемо випадок піцерії. У магазині піци вони продаватимуть кілька різновидів піци, а також додадуть начинки в меню. А тепер уявіть собі ситуацію, коли піца повинна забезпечити ціни за кожну комбінацію піци та начинки. Навіть якщо є чотири основні піци та 8 різних начинок, додаток зійде з розуму, підтримуючи всі ці конкретні комбінації піци та начинки.
Тут виходить візерунок декоратора.
Відповідно до візерунка декораторів, ви будете реалізовувати начинки, як декоратори, а піци будуть прикрашені тими декораторами начинки. Практично кожен замовник бажає начинки за своїм бажанням, а остаточна сума рахунку буде складатися з базових піц та додатково замовлених начинок. Кожен декоратор-топінг знав би про піци, які вона прикрашає, і це ціна. Метод GetPrice () об'єкта Topping поверне сукупну ціну як піци, так і начинки.
Ось приклад коду пояснення вище.
public abstract class BasePizza
{
protected double myPrice;
public virtual double GetPrice()
{
return this.myPrice;
}
}
public abstract class ToppingsDecorator : BasePizza
{
protected BasePizza pizza;
public ToppingsDecorator(BasePizza pizzaToDecorate)
{
this.pizza = pizzaToDecorate;
}
public override double GetPrice()
{
return (this.pizza.GetPrice() + this.myPrice);
}
}
class Program
{
[STAThread]
static void Main()
{
//Client-code
Margherita pizza = new Margherita();
Console.WriteLine("Plain Margherita: " + pizza.GetPrice().ToString());
ExtraCheeseTopping moreCheese = new ExtraCheeseTopping(pizza);
ExtraCheeseTopping someMoreCheese = new ExtraCheeseTopping(moreCheese);
Console.WriteLine("Plain Margherita with double extra cheese: " + someMoreCheese.GetPrice().ToString());
MushroomTopping moreMushroom = new MushroomTopping(someMoreCheese);
Console.WriteLine("Plain Margherita with double extra cheese with mushroom: " + moreMushroom.GetPrice().ToString());
JalapenoTopping moreJalapeno = new JalapenoTopping(moreMushroom);
Console.WriteLine("Plain Margherita with double extra cheese with mushroom with Jalapeno: " + moreJalapeno.GetPrice().ToString());
Console.ReadLine();
}
}
public class Margherita : BasePizza
{
public Margherita()
{
this.myPrice = 6.99;
}
}
public class Gourmet : BasePizza
{
public Gourmet()
{
this.myPrice = 7.49;
}
}
public class ExtraCheeseTopping : ToppingsDecorator
{
public ExtraCheeseTopping(BasePizza pizzaToDecorate)
: base(pizzaToDecorate)
{
this.myPrice = 0.99;
}
}
public class MushroomTopping : ToppingsDecorator
{
public MushroomTopping(BasePizza pizzaToDecorate)
: base(pizzaToDecorate)
{
this.myPrice = 1.49;
}
}
public class JalapenoTopping : ToppingsDecorator
{
public JalapenoTopping(BasePizza pizzaToDecorate)
: base(pizzaToDecorate)
{
this.myPrice = 1.49;
}
}
Це простий приклад додавання нової поведінки до вже існуючого об'єкта динамічно або шаблону «Декоратор». Завдяки природі динамічних мов, таких як Javascript, ця модель стає частиною самої мови.
// Person object that we will be decorating with logging capability
var person = {
name: "Foo",
city: "Bar"
};
// Function that serves as a decorator and dynamically adds the log method to a given object
function MakeLoggable(object) {
object.log = function(property) {
console.log(this[property]);
}
}
// Person is given the dynamic responsibility here
MakeLoggable(person);
// Using the newly added functionality
person.log('name');
switch
або простого if
, ви зможете стверджувати, що це чудовий приклад динамічного додавання поведінки до класу. Але нам потрібно щонайменше два класи, щоб визначити декоратор та декоровані об’єкти за цим шаблоном.
Варто зазначити, що модель введення-виведення Java заснована на шаблоні декораторів. Накладення цього читача поверх того, що читач поверх ... - це справді реальний приклад декоратора світу.
Приклад - Сценарій. Скажімо, ви пишете модуль шифрування. Це шифрування може зашифрувати очищений файл, використовуючи DES - стандарт шифрування даних. Так само в системі ви можете мати шифрування як AES - стандарт попереднього шифрування. Також у вас може бути комбінація шифрування - Спочатку DES, потім AES. Або ви можете спочатку мати AES, потім DES.
Обговорення - Як ви будете обслуговувати цю ситуацію? Ви не можете продовжувати створювати об'єкт таких комбінацій - наприклад, AES та DES - всього 4 комбінації. Таким чином, вам потрібно мати 4 окремих об'єкти. Це стане складним у міру збільшення типу шифрування.
Рішення - Продовжуйте нарощувати стек - комбінації залежно від потреби - під час виконання. Ще одна перевага такого підходу до стеку полягає в тому, що ви можете легко розкрутити його.
Ось рішення - в C ++.
По-перше, вам потрібен базовий клас - фундаментальна одиниця стеку. Ви можете мислити як основу стека. У цьому прикладі це чіткий файл. Давайте завжди слід поліморфізм. Створіть спочатку клас інтерфейсу цієї основної одиниці. Таким чином, ви можете реалізувати це як хочете. Крім того, вам не потрібно думати про залежність, включаючи цю фундаментальну одиницю.
Ось клас інтерфейсу -
class IclearData
{
public:
virtual std::string getData() = 0;
virtual ~IclearData() = 0;
};
IclearData::~IclearData()
{
std::cout<<"Destructor called of IclearData"<<std::endl;
}
Тепер реалізуйте цей клас інтерфейсу -
class clearData:public IclearData
{
private:
std::string m_data;
clearData();
void setData(std::string data)
{
m_data = data;
}
public:
std::string getData()
{
return m_data;
}
clearData(std::string data)
{
setData(data);
}
~clearData()
{
std::cout<<"Destructor of clear Data Invoked"<<std::endl;
}
};
Тепер давайте зробимо абстрактний клас декоратора - який можна розширити, щоб створити будь-який тип смаків - тут аромат - це тип шифрування. Цей абстрактний клас декоратора пов'язаний з базовим класом. Таким чином, декоратор "- це" клас інтерфейсу. Таким чином, потрібно використовувати спадщину.
class encryptionDecorator: public IclearData
{
protected:
IclearData *p_mclearData;
encryptionDecorator()
{
std::cout<<"Encryption Decorator Abstract class called"<<std::endl;
}
public:
std::string getData()
{
return p_mclearData->getData();
}
encryptionDecorator(IclearData *clearData)
{
p_mclearData = clearData;
}
virtual std::string showDecryptedData() = 0;
virtual ~encryptionDecorator() = 0;
};
encryptionDecorator::~encryptionDecorator()
{
std::cout<<"Encryption Decorator Destructor called"<<std::endl;
}
Тепер давайте зробимо клас конкретного декоратора - Тип шифрування - AES -
const std::string aesEncrypt = "AES Encrypted ";
class aes: public encryptionDecorator
{
private:
std::string m_aesData;
aes();
public:
aes(IclearData *pClearData): m_aesData(aesEncrypt)
{
p_mclearData = pClearData;
m_aesData.append(p_mclearData->getData());
}
std::string getData()
{
return m_aesData;
}
std::string showDecryptedData(void)
{
m_aesData.erase(0,m_aesData.length());
return m_aesData;
}
};
Скажімо, типом декоратора є DES -
const std :: string desEncrypt = "Зашифрований DES";
class des: public encryptionDecorator
{
private:
std::string m_desData;
des();
public:
des(IclearData *pClearData): m_desData(desEncrypt)
{
p_mclearData = pClearData;
m_desData.append(p_mclearData->getData());
}
std::string getData(void)
{
return m_desData;
}
std::string showDecryptedData(void)
{
m_desData.erase(0,desEncrypt.length());
return m_desData;
}
};
Давайте зробимо код клієнта для використання цього класу декораторів -
int main()
{
IclearData *pData = new clearData("HELLO_CLEAR_DATA");
std::cout<<pData->getData()<<std::endl;
encryptionDecorator *pAesData = new aes(pData);
std::cout<<pAesData->getData()<<std::endl;
encryptionDecorator *pDesData = new des(pAesData);
std::cout<<pDesData->getData()<<std::endl;
/** unwind the decorator stack ***/
std::cout<<pDesData->showDecryptedData()<<std::endl;
delete pDesData;
delete pAesData;
delete pData;
return 0;
}
Ви побачите такі результати -
HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
DES Encrypted AES Encrypted HELLO_CLEAR_DATA
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Destructor called
Destructor called of IclearData
Encryption Decorator Destructor called
Destructor called of IclearData
Destructor of clear Data Invoked
Destructor called of IclearData
Ось схема UML - класове представлення її. У випадку, якщо ви хочете пропустити код і зосередитись на дизайнерському аспекті.
strategy pattern
?
Шаблон декоратора допомагає вам змінити або налаштувати функціональність вашого об'єкта, з'єднавшись з іншими подібними підкласами цього об’єкта.
Найкращим прикладом можуть бути класи InputStream та OutputStream в пакеті java.io
File file=new File("target","test.txt");
FileOutputStream fos=new FileOutputStream(file);
BufferedOutputStream bos=new BufferedOutputStream(fos);
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.write(5);
oos.writeBoolean(true);
oos.writeBytes("decorator pattern was here.");
//... then close the streams of course.
Що таке шаблон дизайну декораторів на Java.
Офіційне визначення візерунка «Декоратор» з книги GoF (Шаблони дизайну: Елементи багаторазового використання об’єктно-орієнтованого програмного забезпечення, 1995, Pearson Education, Inc. Публікація як Pearson Addison Wesley) говорить, що ви можете,
"Динамічно додайте додаткові обов'язки до об'єкта. Декоратори надають гнучку альтернативу підкласи для розширення функціональності."
Скажімо, у нас є Піца, і ми хочемо прикрасити її начинками, такими як Куряча Масала, Цибуля та Сир Моцарелла. Давайте подивимося, як це реалізувати на Java ...
Програма для демонстрації того, як реалізувати шаблон дизайну декораторів на Java.
Pizza.java:
<!-- language-all: lang-html -->
package com.hubberspot.designpattern.structural.decorator;
public class Pizza {
public Pizza() {
}
public String description(){
return "Pizza";
}
}
package com.hubberspot.designpattern.structural.decorator;
public abstract class PizzaToppings extends Pizza {
public abstract String description();
}
package com.hubberspot.designpattern.structural.decorator;
public class ChickenMasala extends PizzaToppings {
private Pizza pizza;
public ChickenMasala(Pizza pizza) {
this.pizza = pizza;
}
@Override
public String description() {
return pizza.description() + " with chicken masala, ";
}
}
package com.hubberspot.designpattern.structural.decorator;
public class MozzarellaCheese extends PizzaToppings {
private Pizza pizza;
public MozzarellaCheese(Pizza pizza) {
this.pizza = pizza;
}
@Override
public String description() {
return pizza.description() + "and mozzarella cheese.";
}
}
package com.hubberspot.designpattern.structural.decorator;
public class Onion extends PizzaToppings {
private Pizza pizza;
public Onion(Pizza pizza) {
this.pizza = pizza;
}
@Override
public String description() {
return pizza.description() + "onions, ";
}
}
package com.hubberspot.designpattern.structural.decorator;
public class TestDecorator {
public static void main(String[] args) {
Pizza pizza = new Pizza();
pizza = new ChickenMasala(pizza);
pizza = new Onion(pizza);
pizza = new MozzarellaCheese(pizza);
System.out.println("You're getting " + pizza.description());
}
}
Я широко використовував візерунок декораторів у своїй роботі. Я зробив пост у своєму блозі про те, як використовувати його під час ведення журналу.
Шаблон декоратора дозволяє динамічно додавати поведінку до об'єктів.
Візьмемо приклад, коли вам потрібно створити додаток, який розраховує ціну різних видів гамбургерів. Потрібно обробляти різні варіації гамбургерів, наприклад, "великий" або "з сиром", кожен з яких має ціну відносно базового бургер. Наприклад, додайте 10 доларів за бургер з сиром, додайте додаткові 15 доларів за великий бургер і т.д.
У цьому випадку вам може сподобатися створити підкласи для їх обробки. Ми можемо виразити це в Ruby як:
class Burger
def price
50
end
end
class BurgerWithCheese < Burger
def price
super + 15
end
end
У наведеному вище прикладі клас BurgerWithCheese успадковує від Burger і переосмислює метод ціни, щоб додати 15 доларів до ціни, визначеної в суперкласі. Ви також створили б клас LargeBurger і визначили ціну відносно Burger. Але також потрібно визначити новий клас для поєднання «великий» та «з сиром».
Тепер що станеться, якщо нам потрібно подавати «бургер з картоплею фрі»? У нас вже є 4 класи для обробки цих комбінацій, і нам потрібно буде додати ще 4, щоб обробити всі поєднання трьох властивостей - "великий", "з сиром" і "з фрі". Зараз нам потрібно 8 класів. Додайте іншу властивість і нам знадобиться 16. Це виросте як 2 ^ n.
Натомість спробуємо визначити BurgerDecorator, який бере об’єкт Burger:
class BurgerDecorator
def initialize(burger)
self.burger = burger
end
end
class BurgerWithCheese < BurgerDecorator
def price
self.burger.price + 15
end
end
burger = Burger.new
cheese_burger = BurgerWithCheese.new(burger)
cheese_burger.price # => 65
У наведеному вище прикладі ми створили клас BurgerDecorator, від якого наслідує клас BurgerWithCheese. Ми також можемо представити "велику" варіацію, створивши клас LargeBurger. Тепер ми могли б визначити великий бургер із сиром під час виконання:
b = LargeBurger.new(cheese_burger)
b.price # => 50 + 15 + 20 = 85
Пам'ятайте, як використання успадкування для додавання варіації "з фрі" передбачало б додавання ще 4 підкласів? З декораторами ми просто створимо один новий клас, BurgerWithFries, щоб обробити нову варіацію і вирішити цю проблему під час виконання. Кожному новому об’єкту знадобиться просто більше декоратора, щоб покрити всі перестановки.
PS. Це коротка версія статті, про яку я писав про використання візерунка декораторів у Ruby , яку ви можете прочитати, якщо хочете дізнатись більш детальні приклади.
Декоратор:
Див sourcemaking статті для більш докладної інформації.
Декоратор (Анотація) : це абстрактний клас / інтерфейс, який реалізує компонентний інтерфейс. Він містить компонентний інтерфейс. За відсутності цього класу вам потрібно багато підкласів ConcreteDecorators для різних комбінацій. Склад компонента зменшує непотрібні підкласи.
Приклад JDK:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("a.txt")));
while(bis.available()>0)
{
char c = (char)bis.read();
System.out.println("Char: "+c);;
}
Подивіться нижче на питання SE для прикладів UML-діаграми та прикладів коду.
Корисні статті:
Справжній приклад слова візерунка декоратора: VendingMachineDecorator пояснили @
Коли використовувати візерунок декоратора?
Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
beverage.decorateBeverage();
beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
beverage.decorateBeverage();
У наведеному вище прикладі чай або кава (напій) прикрашені цукром і лимоном.
Шаблон декоратора досягає єдиної мети - динамічно додавати обов'язки до будь-якого об’єкта .
Модель Java I / O заснована на шаблоні декораторів.
У Вікіпедії є приклад про декорування вікна смугою прокрутки:
http://en.wikipedia.org/wiki/Decorator_pattern
Ось ще один дуже "реальний" приклад "Член команди, керівник команди та менеджер", який ілюструє, що модель декоратора незамінна простим успадкуванням:
https://zishanbilal.wordpress.com/2011/04/28/design-patterns-by-examples-decorator-pattern/
Деякий час назад я відновив кодову базу з використанням шаблону Decorator, тому спробую пояснити випадок використання.
Припустимо, у нас є набір послуг, і виходячи з того, чи отримав користувач ліцензію певної послуги, нам потрібно запустити послугу.
Усі сервіси мають спільний інтерфейс
interface Service {
String serviceId();
void init() throws Exception;
void start() throws Exception;
void stop() throws Exception;
}
abstract class ServiceSupport implements Service {
public ServiceSupport(String serviceId, LicenseManager licenseManager) {
// assign instance variables
}
@Override
public void init() throws Exception {
if (!licenseManager.isLicenseValid(serviceId)) {
throw new Exception("License not valid for service");
}
// Service initialization logic
}
}
Якщо ви уважно спостерігаєте, ServiceSupport
залежить від LicenseManager
. Але від чого це має залежати LicenseManager
? Що робити, якщо нам потрібна довідкова служба, яка не потребує перевірки ліцензійної інформації. У ситуації, що склалася, нам доведеться якось навчатись, LicenseManager
щоб повернутися true
до фонових послуг. Такий підхід мені не здався вдалим. На думку мене, перевірка ліцензії та інша логіка були ортогональними один для одного.
Тож на допомогу приходить Pattern Decorator і тут починається рефакторинг з TDD.
class LicensedService implements Service {
private Service service;
public LicensedService(LicenseManager licenseManager, Service service) {
this.service = service;
}
@Override
public void init() {
if (!licenseManager.isLicenseValid(service.serviceId())) {
throw new Exception("License is invalid for service " + service.serviceId());
}
// Delegate init to decorated service
service.init();
}
// override other methods according to requirement
}
// Not concerned with licensing any more :)
abstract class ServiceSupport implements Service {
public ServiceSupport(String serviceId) {
// assign variables
}
@Override
public void init() {
// Service initialization logic
}
}
// The services which need license protection can be decorated with a Licensed service
Service aLicensedService = new LicensedService(new Service1("Service1"), licenseManager);
// Services which don't need license can be created without one and there is no need to pass license related information
Service aBackgroundService = new BackgroundService1("BG-1");
Візьмемо для прикладу PubG. Гвинтівки штурму найкраще спрацьовують із 4-кратним збільшенням, і, поки ми на ньому, нам також знадобляться компенсатор і супресор. Це зменшить віддалення та зменшить звук стрільби, а також відлуння. Нам потрібно буде реалізувати цю функцію там, де ми дозволимо гравцям придбати улюблену зброю та їхні аксесуари. Гравці можуть придбати пістолет або якийсь аксесуар або весь аксесуар, і за них платять відповідно.
Давайте подивимось, як тут застосовується візерунок декораторів:
Припустимо, хтось хоче придбати SCAR-L з усіма трьома згаданими вище аксесуарами.
Це призведе до такої схеми класів:
Тепер у нас можуть бути такі заняття:
public abstract class Gun {
private Double cost;
public Double getCost() {
return cost;
}
}
public abstract class GunAccessories extends Gun { }
public class Scarl extends Gun {
public Scarl() {
cost = 100;
}
}
public class Suppressor extends GunAccessories {
Gun gun;
public Suppressor(Gun gun) {
cost = 5;
this.gun = gun;
}
public double getCost(){
return cost + gun.getCost();
}
}
public class GunShop{
public static void main(String args[]){
Gun scarl = new Scarl();
scarl = new Supressor(scarl);
System.out.println("Price is "+scarl.getCost());
}
}
Ми можемо також додати інші аксесуари і прикрасити нашу зброю.
Довідка:
https://nulpointerexception.com/2019/05/05/a-beginner-guide-to-decorator-pattern/
Шаблон дизайну декораторів : Цей шаблон допомагає змінити характеристики об'єкта під час виконання. Він надає об'єкту різні ароматизатори та надає гнучкість у виборі, які інгредієнти ми хочемо використати для цього аромату.
Приклад із реального життя: Скажімо, у вас є основне сидіння в кабіні під час польоту. Тепер вам дозволяється вибирати кілька зручностей із сидінням. Кожна цінність пов'язана з нею. Тепер, якщо користувач вибирає Wi-Fi та преміум-продукти, йому / їй платять за сидіння + wifi + преміум-їжу.
У цьому випадку візерунок дизайну декораторів справді може нам допомогти. Перейдіть на вищенаведене посилання, щоб дізнатися більше про візерунок декораторів та реалізацію одного прикладу реального життя.