Необходимо получить декоратор из компонента в шаблоне проектирования декоратора - PullRequest
2 голосов
/ 04 августа 2020

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

Пример, который я видел, был примерно таким:

//Component classes
public abstract class Car
{
   public abstract int GetPrice();
   //Other properties and methods
}

public class ActualCar : Car
{
   public int GetPrice(){return 1000;}
}

//Decorators classes
public class CarDecorator : Car //No idea why this is necessary
{
   protected Car car;
   public CarDecorator(Car car)
   {
      this.car = car;
   }

   public override int GetPrice() => this.car.GetPrice();
}

public class LeatherSeats : CarDecorator
{
   public LeatherSeats(Car car) : base(car){}
   public override int GetPrice() => this.car.GetPrice() + 200;
}

public class AlloyWheels : CarDecorator
{
   public AlloyWheels(Car car) : base(car) {}
   public override int GetPrice() => this.car.GetPrice() + 150;
}

Теперь при использовании компонента вместе с его декораторами мы используем его как:

Car newCar = new ActualCar();
int price = new AlloyWheels(new LeatherSeats(newCar)).GetPrice();

Теперь мне показалось странным, что CarDecorator унаследован от Car, как бы вы ни посмотрите, это не соответствует типу отношений. Итак, я просмотрел еще несколько примеров и понял, что именно так разработан шаблон декоратора.

Я не хочу спрашивать, почему шаблон декоратора был разработан таким образом, но просто хочу знать, что будет недостатком в том, что шаблон декоратора не будет производным от компонента, который он обертывает.

Ответы [ 2 ]

2 голосов
/ 04 августа 2020

Ваш CarDecorator, который является базовым декоратором, должен наследовать интерфейс Car (здесь это абстрактный класс), чтобы гарантировать, что CarDecorator реализовал (должен переопределить) метод GetPrice.

If CarDecorator не наследуется от Car, тогда вы не можете сделать это так:

Car newCar = new ActualCar();
int price = new CarDecorator(newCar).GetPrice();

Car здесь служит почти как интерфейс или контракт, который явно говорит, что все конкретные декораторы должны реализовать метод GetPrice.

1 голос
/ 05 августа 2020

Цель шаблона декоратора - предоставить способ расширения класса без создания подклассов. для расширения функциональности ". (Банда четырех)

Это означает, что для клиента тип декоратора должен вести себя точно так же, как (mimi c) декорированный тип. Более того, тип декоратора должен быть заменяемым на декорированный тип.

Чтобы сделать типы заменяемыми, они должны быть одного и того же базового типа:

interface IVehicle {}

class Car : IVehicle {}

class Bus : IVehicle {}

Теперь можно заменять все IVehicle:

IVehicle car = new Car();
IVehicle bus = new Bus();

IVehicle vehicle = car;
vehicle = bus;

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

// Common base class. Will be extended by a decorator.
public interface ICar
{
  ISound Honk();

  //Other properties and methods
}

public class Beagle : ICar 
{
  // Implementation of ICar
  public ISound Honk() => new BeagleHornSound(); // BeagleHornSound implements ISound
}

// Abstract decorator base class.
// Since the base class implements ICar, every CarDecorator "is-a" ICar.
// This makes CarDecorator and every ICar replaceable and allows the CarDecorator to mimic the decorated/wrapped ICar. 
// Therefore every feature of this class or subclass is actually extending every ICar.
public abstract class CarDecorator : ICar
{
  private ICar Car { get; set; }
  private CarDecorator(ICar car)
  {
    this.Car = car;
  }

  // Implementation of ICar
  public ISound Honk() => this.Car.Honk();
}

// Concrete decorator class
public abstract class Lowrider : CarDecorator
{
  private Hydraulics Hydraulics { get; set; }
  
  public CarDecorator(ICar car) : base (car)
  {
    this.Hydraulics = new Hydraulics();
  }

  public void EnableHydraulic() => LetCarHop();
}

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

// Extends the ICar by wrapping it into a decorator
public CarDecorator PimpCar(ICar car) 
{
  return new Lowrider(car);  
}

ICar beagle = new Beagle(); // Beagle implements ICar

CarDecorator lowrider = PimpCar(beagle); // Returns a Lowrider which extends CarDecorator

// Since the decorator also implements ICar, every CarDecorator is replaceable with every other ICar
ICar pimpedBeagle = lowrider; // Lowrider implements ICar

// Since the decorator "is-a" ICar, it can mimic every ICar
ISound hornSound = lowrider.Honk();

// Since the Lowrider is decorating/wrapping currently a Beagle, it actually mimics a Beagle
bool isSameType = lowrider.Honk() is BeagleHornSound; // true

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

Обычно тип расширяется путем наследования от него и добавления новых функций в подкласс. Суперкласс (предок) расширяется, потому что подкласс (потомок) по-прежнему имеет тот же тип, что и его суперкласс (это наследование - то же дерево наследования). Декоратор - это просто еще одна стратегия для достижения того же результата, но без подкласса типа, который вы sh расширяете.

Тип декоратора (потомок) на самом деле является расширенной версией декорированного типа (предок). Как будто декоратор является подклассом (расширением) декорированного типа. Чтобы реализовать это с точки зрения иерархии типов, декоратор должен быть того же типа, что и суперкласс, чтобы быть истинным потомком. По сути, декоратор имитирует наследование с помощью композиции. Или декоратор - это более гибкая форма наследования, поскольку каждый тип, заключенный в один декоратор, фактически расширяется без написания нового производного класса завернутого типа.

...