Design patterns
Декоратор
Поиск…
Вступление
Шаблон Decorator позволяет пользователю добавлять новые функции к существующему объекту без изменения его структуры. Этот тип шаблона проектирования имеет структурную структуру, поскольку этот шаблон действует как оболочка существующего класса.
Этот шаблон создает класс декоратора, который обертывает исходный класс и предоставляет дополнительную функциональность, сохраняя целостность подписи класса.
параметры
параметр | Описание |
---|---|
напиток | это может быть чай или кофе |
VendingMachineDecorator
Определение декоратора согласно Wikiepdia:
Шаблон Decorator можно использовать для расширения (украшения) функций определенного объекта статически или, в некоторых случаях, во время выполнения, независимо от других экземпляров одного и того же класса, при условии, что некоторые разработки выполняются во время разработки.
Decorator добавляет дополнительные обязанности к объекту динамически. Декораторы обеспечивают гибкую альтернативу подклассу для расширения функциональности.
Рисунок декоратора содержит четыре компонента.
- Компонентный интерфейс: он определяет интерфейс для выполнения определенных операций
- ConcreteComponent: он реализует операции, определенные в интерфейсе Component
- Decorator (Abstract): это абстрактный класс, расширяющий интерфейс компонента. Он содержит компонентный интерфейс. В отсутствие этого класса вам нужно много подклассов ConcreteDecorators для разных комбинаций. Состав компонента уменьшает ненужные подклассы.
- ConcreteDecorator: он поддерживает реализацию Abstract Decorator.
Возвращаясь к примеру кода,
- Напиток является компонентом. Он определяет абстрактный метод: decorateBeverage
- Чай и кофе - это конкретные реализации напитка .
- BeverageDecorator - абстрактный класс, который содержит напиток
- SugarDecorator и LemonDecorator - это бетонные декораторы для BeverageDecorator.
EDIT: изменил пример, чтобы отразить реальный мировой сценарий вычисления цены на напитки, добавив один или несколько ароматов, таких как сахар, лимон и т. Д. (Ароматизаторы - декораторы)
abstract class Beverage {
protected String name;
protected int price;
public Beverage(){
}
public Beverage(String name){
this.name = name;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
protected void setPrice(int price){
this.price = price;
}
protected int getPrice(){
return price;
}
protected abstract void decorateBeverage();
}
class Tea extends Beverage{
public Tea(String name){
super(name);
setPrice(10);
}
public void decorateBeverage(){
System.out.println("Cost of:"+ name +":"+ price);
// You can add some more functionality
}
}
class Coffee extends Beverage{
public Coffee(String name){
super(name);
setPrice(15);
}
public void decorateBeverage(){
System.out.println("Cost of:"+ name +":"+ price);
// You can add some more functionality
}
}
abstract class BeverageDecorator extends Beverage {
protected Beverage beverage;
public BeverageDecorator(Beverage beverage){
this.beverage = beverage;
setName(beverage.getName()+"+"+getDecoratedName());
setPrice(beverage.getPrice()+getIncrementPrice());
}
public void decorateBeverage(){
beverage.decorateBeverage();
System.out.println("Cost of:"+getName()+":"+getPrice());
}
public abstract int getIncrementPrice();
public abstract String getDecoratedName();
}
class SugarDecorator extends BeverageDecorator{
public SugarDecorator(Beverage beverage){
super(beverage);
}
public void decorateBeverage(){
super.decorateBeverage();
decorateSugar();
}
public void decorateSugar(){
System.out.println("Added Sugar to:"+beverage.getName());
}
public int getIncrementPrice(){
return 5;
}
public String getDecoratedName(){
return "Sugar";
}
}
class LemonDecorator extends BeverageDecorator{
public LemonDecorator(Beverage beverage){
super(beverage);
}
public void decorateBeverage(){
super.decorateBeverage();
decorateLemon();
}
public void decorateLemon(){
System.out.println("Added Lemon to:"+beverage.getName());
}
public int getIncrementPrice(){
return 3;
}
public String getDecoratedName(){
return "Lemon";
}
}
public class VendingMachineDecorator {
public static void main(String args[]){
Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
beverage.decorateBeverage();
beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
beverage.decorateBeverage();
}
}
выход:
Cost of:Assam Tea:10
Cost of:Assam Tea+Lemon:13
Added Lemon to:Assam Tea
Cost of:Assam Tea+Lemon+Sugar:18
Added Sugar to:Assam Tea+Lemon
Cost of:Cappuccino:15
Cost of:Cappuccino+Lemon:18
Added Lemon to:Cappuccino
Cost of:Cappuccino+Lemon+Sugar:23
Added Sugar to:Cappuccino+Lemon
В этом примере вычисляется стоимость напитка в торговом автомате после добавления многих напитков в напиток.
В приведенном выше примере:
Стоимость чая = 10, лимон = 3 и сахар = 5. Если вы делаете сахар + лимон + чай, он стоит 18.
Стоимость кофе = 15, лимон = 3 и сахар = 5. Если вы делаете сахар + лимон + кофе, он стоит 23
Используя тот же Decorator для обоих напитков (Tea and Coffee), количество подклассов было уменьшено. В отсутствие шаблона Decorator у вас должны быть разные подклассы для разных комбинаций.
Комбинации будут такими:
SugarLemonTea
SugarTea
LemonTea
SugarLemonCapaccuino
SugarCapaccuino
LemonCapaccuino
и т.п.
Используя тот же Decorator
для обоих напитков, количество подклассов было уменьшено. Это возможно из-за composition
а не концепции inheritance
используемой в этом шаблоне.
Сравнение с другими шаблонами дизайна (Из sourcemaking статьи)
Адаптер предоставляет другой интерфейс для своего объекта. Прокси обеспечивает тот же интерфейс. Декоратор обеспечивает расширенный интерфейс.
Адаптер изменяет интерфейс объекта, Decorator повышает обязанности объекта.
Композитный и декоратор имеют аналогичные структурные диаграммы, отражающие тот факт, что оба полагаются на рекурсивную композицию для организации открытого числа объектов
Decorator предназначен для добавления обязанностей к объектам без подкласса. Концентрация композита не на украшении, а на представлении
Декораторы и Прокси имеют разные цели, но аналогичные структуры
Декоратор позволяет вам изменять скин объекта. Стратегия позволяет вам изменить кишки.
Ключевые варианты использования:
- Добавление динамических функций / обязанностей
- Устранение функциональности / ответственности динамически
- Избегайте слишком большого количества суб-классификации, чтобы добавить дополнительные обязанности.
Кэширование декоратора
В этом примере показано, как добавить возможности кэширования в DbProductRepository
с использованием шаблона Decorator. Этот подход придерживается принципов SOLID, поскольку он позволяет добавлять кеширование без нарушения принципа единой ответственности или принципа Open / closed .
public interface IProductRepository
{
Product GetProduct(int id);
}
public class DbProductRepository : IProductRepository
{
public Product GetProduct(int id)
{
//return Product retrieved from DB
}
}
public class ProductRepositoryCachingDecorator : IProductRepository
{
private readonly IProductRepository _decoratedRepository;
private readonly ICache _cache;
private const int ExpirationInHours = 1;
public ProductRepositoryCachingDecorator(IProductRepository decoratedRepository, ICache cache)
{
_decoratedRepository = decoratedRepository;
_cache = cache;
}
public Product GetProduct(int id)
{
var cacheKey = GetKey(id);
var product = _cache.Get<Product>(cacheKey);
if (product == null)
{
product = _decoratedRepository.GetProduct(id);
_cache.Set(cacheKey, product, DateTimeOffset.Now.AddHours(ExpirationInHours));
}
return product;
}
private string GetKey(int id) => "Product:" + id.ToString();
}
public interface ICache
{
T Get<T>(string key);
void Set(string key, object value, DateTimeOffset expirationTime)
}
Использование:
var productRepository = new ProductRepositoryCachingDecorator(new DbProductRepository(), new Cache());
var product = productRepository.GetProduct(1);
Результатом вызова GetProduct
будет: извлечение продукта из кеша (ответственность декоратора), если продукт не был в кеше, отправляется с вызовом в DbProductRepository
и извлекает продукт из БД. После этого продукт может быть добавлен в кеш, поэтому последующие вызовы не будут попадать в БД.