Reputation: 4362
Can the decorator pattern be used to create / build a type?
For example, lets say we want to hydrate an object. Arguably we can have a builder class that does it all but what if its a complex object with multiple layers, is it valid to have multiple decorators that hydrate a portion of that object and return it back to the caller?
For instance:
FirstDecorator
which calls an API and the result we instantiate the type and hydrate parts of it. Because this was a successful call, we will invoke the next decorator.SecondDecorator
then invokes an API but hydrates another aspect of the object.I like this approach because I can have multiple decorators that hydrate a specific portion of the object and easily be extended by simply adding another decorator to the sequence.
The problem here is with definition and is this leaning towards Chain of Responsibility (CoR)? My understanding is that CoR allows you to terminate the chain at anytime and not allow the other processors to act. Decorator pattern implies that all must be executed? The difference between the two is very sketchy to me.
The chain-of-responsibility pattern is structurally nearly identical to the decorator pattern, the difference being that for the decorator, all classes handle the request, while for the chain of responsibility, exactly one of the classes in the chain handles the request. This is a strict definition of the Responsibility concept in the GoF book. However, many implementations (such as loggers below, or UI event handling, or servlet filters in Java, etc) allow several elements in the chain to take responsibility.
The code example I have:
public interface IPokemonDecorator
{
// Every decorator will hydrate a part of PokeApiResponse
Task Decorate(PokeApiResponse pokeApiResponse, PokeRequest request);
}
public class BasicInfoDecorator
{
private readonly IPokemonDecorator _pokemonDecorator;
private readonly IPokemonApiAdapter _pokemonApiAdapter;
public BasicInfoDecorator(
IPokemonDecorator pokemonDecorator,
IPokemonApiAdapter pokemonApiAdapter)
{
_pokemonDecorator = pokemonDecorator;
_pokemonApiAdapter = pokemonApiAdapter;
}
public async Task Decorate(PokeApiResponse pokeApiResponse, PokeRequest request)
{
var httpResponse = await _pokemonApiAdapter.Execute($"api/v2/pokemon/{request.Name}", request);
pokeApiResponse = await httpResponse.Content.ReadFromJsonAsync<PokeApiResponse>();
if (pokeApiResponse != null)
{
request.Id = pokeApiResponse.Id.ToString();
await _pokemonDecorator.Decorate(pokeApiResponse, request);
}
}
}
public class DetailedInfoDecorator
{
private readonly PokeApiAdapter _pokemonApiAdapter;
public DetailedInfoDecorator(PokeApiAdapter pokemonApiAdapter)
{
_pokemonApiAdapter = pokemonApiAdapter;
}
public async Task Decorate(PokeApiResponse pokeApiResponse, PokeRequest request)
{
var httpResponse = await _pokemonApiAdapter.Execute($"api/v2/pokemon-species/{request.Id}", request);
var details = await httpResponse.Content.ReadFromJsonAsync<SpeciesDetails>();
pokeApiResponse.SpeciesDetails = GetDescriptionOrDefault(request, details);
}
private static SpeciesDetails GetDescriptionOrDefault(PokeRequest request, SpeciesDetails speciesDetails)
{
// some logic to get description based on default language or requested language
throw new NotImplementedException();
}
}
Arguably can do the same using an application service that orchestrates the calls and gets the responses and then passes it to a builder to construct the object.
Is this a violation of the principle?
Upvotes: 0
Views: 184