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.