I would like to know how this pattern can be implemented, but not according to the “standard” scheme proposed by GOF. I give an example of a "standard" implementation.

class Component { Component() { ... } } class ConcreteComponent : Component { ConcreteComponent() { ... } } class Decorator : Component { Component c; int newState; public void SetComponent(Component component) { ... } Decorator() { ... } } 

Is it possible to decorate objects somehow differently, without combining aggregation and inheritance, but so that all the advantages of the pattern are preserved? Preferably in C #.

  • one
    The book Patterns of parallel programming by Stephen Toub (the link can be officially downloaded for free) in the Decorator to pipilene section (p. 56) provides an example of a decorator implementation for streams, which allows parallelizing intensive calculations, such as encryption and compression. In general, the book is a storehouse of useful tricks. - Alexander Petrov

1 answer 1

I usually use decorators along with the interface and composition.

For example, there is some source of weather data:

 public interface IWeatherProvider { public WeatherInfo GetWeather(string city); } 

There is a default implementation. For example, it goes over http to the data provider:

 public class DefaultWeatherProvider : IWeatherProvider { public WeatherInfo GetWeather(string city) { // обращение по HTTP } } 

In some scenarios, it is not necessary for us to request the weather each time from the provider, but rather to cache it:

 public class CachingWeatherProvider : IWeatherProvider { private readonly Dictionary<string, WeatherInfo> _cache; private readonly IWeatherProvider _provider; public CachingWeatherProvider(IWeatherProvider provider) { _provider = provider; _cache = new Dictionary<string, WeatherInfo>(); } public WeatherInfo GetWeather(string city) { WeatherInfo weather; if (!_cache.TryGetValue(city, out weather)) { weather = _provider.GetWeather(city); _cache[city] = weather; } return weather; } } 

We use:

 var provider = new CachingWeatherProvider(new DefaultWeatherProvider()); 

Or suddenly the source is unreliable, often lies, and you want to add a retry:

 public class RetryingWeatherProvider : IWeatherProvider { private readonly IWeatherProvider _provider; private int _maxRetries; public RetryingWeatherProvider(IWeatherProvider provider, int maxRetries) { _provider = provider; _maxRetries = maxRetries; } public WeatherInfo GetWeather(string city) { int retry = 0; do { try { return _provider.GetWeather(city); } catch (WebException) { if (retry == _maxRetries) { throw; } else { // подождем немного } } } while (++retry < _maxRetries) } } 

And then you can bloat a provider with a cache and a retract:

 var provider = new CachingWeatherProvider( new RetryingWeatherProvider(new DefaultWeatherProvider(), 3)); 

How exactly a decorator is implemented is not important in principle, differences have the right to life. It is only important to remember the open-closed principle : the decorator is very useful when you need to extend the functionality of the code, without touching the main code. You have a data provider. To add retro functionality, you don’t climb into the main provider, but create a decorator. To add caching, you again do not climb into the provider, but create another decorator, etc.

PS All the above code is not production ready, but only an illustration.

  • Thanks for an interesting example. Sorry I can not ply! Here again, there is an implementation of the interface and there is a composition :) I liked how you create the object at the end. The question was caused by a reaction to my previous question about the “search for patterns in the source code,” the reaction was such that they could be implemented in different ways, so they could not be found in the code. So I am trying to come up with "non-standard" implementations of design patterns - PashaKrizskiy
  • one
    @PashaKrizskiy is definitely not found in the code, because pure patterns have little relevance to real-world tasks. They describe ways to solve some problems in isolation, whereas in real code you can use several patterns simultaneously in a rather modest area of ​​code. Approximately as sections of theoretical physics, they undoubtedly describe specific phenomena, but only from one side, while the described phenomenon itself is immeasurably more complicated. Andreycha , and an example is really cool, I will bookmark it. - rdorn