C
C#2y ago
Hulkstance

Decorator vs Adapter pattern

I wonder what the difference between the two is
Match interfaces of different classes
Decorator: Add responsibilities to objects dynamically
5 Replies
Hulkstance
HulkstanceOP2y ago
Decorator
public interface INatsClientConnectionFactory
{
IConnection CreateConnection(Action<Options>? configureOptions = null);

IConnection CreateConnection(Options options);

IEncodedConnection CreateEncodedConnection(Action<Options>? configureOptions = null);

IEncodedConnection CreateEncodedConnection(Options options);
}


public sealed class NatsClientConnectionFactoryDecorator : INatsClientConnectionFactory
{
private readonly ConnectionFactory _connectionFactory;

public NatsClientConnectionFactoryDecorator(ConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}

public IConnection CreateConnection(Action<Options>? configureOptions = default)
{
var options = ConnectionFactory.GetDefaultOptions();
configureOptions?.Invoke(options);
return CreateConnection(options);
}

public IConnection CreateConnection(Options options)
{
return _connectionFactory.CreateConnection(options);
}

public IEncodedConnection CreateEncodedConnection(Action<Options>? configureOptions = default)
{
var options = ConnectionFactory.GetDefaultOptions();
configureOptions?.Invoke(options);
return CreateEncodedConnection(options);
}

public IEncodedConnection CreateEncodedConnection(Options options)
{
return _connectionFactory.CreateEncodedConnection(options);
}
}
public interface INatsClientConnectionFactory
{
IConnection CreateConnection(Action<Options>? configureOptions = null);

IConnection CreateConnection(Options options);

IEncodedConnection CreateEncodedConnection(Action<Options>? configureOptions = null);

IEncodedConnection CreateEncodedConnection(Options options);
}


public sealed class NatsClientConnectionFactoryDecorator : INatsClientConnectionFactory
{
private readonly ConnectionFactory _connectionFactory;

public NatsClientConnectionFactoryDecorator(ConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}

public IConnection CreateConnection(Action<Options>? configureOptions = default)
{
var options = ConnectionFactory.GetDefaultOptions();
configureOptions?.Invoke(options);
return CreateConnection(options);
}

public IConnection CreateConnection(Options options)
{
return _connectionFactory.CreateConnection(options);
}

public IEncodedConnection CreateEncodedConnection(Action<Options>? configureOptions = default)
{
var options = ConnectionFactory.GetDefaultOptions();
configureOptions?.Invoke(options);
return CreateEncodedConnection(options);
}

public IEncodedConnection CreateEncodedConnection(Options options)
{
return _connectionFactory.CreateEncodedConnection(options);
}
}
Adapter
public interface ILoggerAdapter<TType>
{
void LogInformation(string? message, params object?[] args);

void LogError(Exception? exception, string? message, params object?[] args);
}

public class LoggerAdapter<TType> : ILoggerAdapter<TType>
{
private readonly ILogger<TType> _logger;

public LoggerAdapter(ILogger<TType> logger)
{
_logger = logger;
}

public void LogInformation(string? message, params object?[] args)
{
_logger.LogInformation(message, args);
}

public void LogError(Exception? exception, string? message, params object?[] args)
{
_logger.LogError(exception, message, args);
}
}
public interface ILoggerAdapter<TType>
{
void LogInformation(string? message, params object?[] args);

void LogError(Exception? exception, string? message, params object?[] args);
}

public class LoggerAdapter<TType> : ILoggerAdapter<TType>
{
private readonly ILogger<TType> _logger;

public LoggerAdapter(ILogger<TType> logger)
{
_logger = logger;
}

public void LogInformation(string? message, params object?[] args)
{
_logger.LogInformation(message, args);
}

public void LogError(Exception? exception, string? message, params object?[] args)
{
_logger.LogError(exception, message, args);
}
}
I personally use that logging adapter in cases where I have to test _logger.LogInformation, because you know LogInformation is an extension method and it doesn't inherit from an interface, so you can't test it. The LoggingAdapter makes it testable. I kinda use the Decorator above to extend the capabilities of the ConnectionFactory and in this case to add configuration from appsettings or Action<T>
public sealed class DynamicClientUserTradeRepositoryAdapter : IUserTradeRepository
{
private readonly IDynamoDbClientFactory _factory;
private IUserTradeRepository? _repository;

public DynamicClientUserTradeRepositoryAdapter(IDynamoDbClientFactory factory)
{
_factory = factory;
}

public async Task<UserTradeDto?> GetAsync(string hashKey, string sortKey)
{
var repository = await GetRepositoryAsync();
return await repository.GetAsync(hashKey, sortKey);
}

public async Task<bool> CreateAsync(UserTradeDto userTradeDto)
{
var repository = await GetRepositoryAsync();
return await repository.CreateAsync(userTradeDto);
}

public async Task<bool> UpdateAsync(UserTradeDto userTradeDto)
{
var repository = await GetRepositoryAsync();
return await repository.UpdateAsync(userTradeDto);
}

public async Task<bool> DeleteAsync(string hashKey, string sortKey)
{
var repository = await GetRepositoryAsync();
return await repository.DeleteAsync(hashKey, sortKey);
}

private async Task<IUserTradeRepository> GetRepositoryAsync()
{
if (_repository is null)
{
var client = await _factory.GetClientAsync();
_repository = new UserTradeRepository(client);
}

return _repository;
}
}
public sealed class DynamicClientUserTradeRepositoryAdapter : IUserTradeRepository
{
private readonly IDynamoDbClientFactory _factory;
private IUserTradeRepository? _repository;

public DynamicClientUserTradeRepositoryAdapter(IDynamoDbClientFactory factory)
{
_factory = factory;
}

public async Task<UserTradeDto?> GetAsync(string hashKey, string sortKey)
{
var repository = await GetRepositoryAsync();
return await repository.GetAsync(hashKey, sortKey);
}

public async Task<bool> CreateAsync(UserTradeDto userTradeDto)
{
var repository = await GetRepositoryAsync();
return await repository.CreateAsync(userTradeDto);
}

public async Task<bool> UpdateAsync(UserTradeDto userTradeDto)
{
var repository = await GetRepositoryAsync();
return await repository.UpdateAsync(userTradeDto);
}

public async Task<bool> DeleteAsync(string hashKey, string sortKey)
{
var repository = await GetRepositoryAsync();
return await repository.DeleteAsync(hashKey, sortKey);
}

private async Task<IUserTradeRepository> GetRepositoryAsync()
{
if (_repository is null)
{
var client = await _factory.GetClientAsync();
_repository = new UserTradeRepository(client);
}

return _repository;
}
}
Another use case of mine where my code depends on DynamoDbClientFactory.GetClientAsync which is asynchronous and is coming from an external library, meaning I cannot really make it synchronous for the AddSingleton<T> ctor, so I needed a workaround and there it is so I'm not sure how to distinguish between the two: Decorator and Adapter In my opinion, the intent of Adapter is not to add feature (Decorator), but to convert old feature to a new interface what do you think?
Mayor McCheese
Common theoretical examples of decorators are things like coffee or pizza. Pizza class, large pizza class, ham and pineapple pizza class, etc. instead of multiple hundreds of classes a few classes are used to decorate functionality. Adapter is different, it is designed to facilitate communication between classes. You might have different versions of a database to interact with, different logging systems etc.
Mayor McCheese
What part is confusing? Can you elaborate on where you are confused? NB: a lot of design patterns have similar goals and attributes. I encourage people to learn about various design patterns; but there are very few pure situations that fit a problem to a pattern so don't force your problem into rigid pre-defined solutions; instead take the parts that matter for your problem and use them as you need.
Hulkstance
HulkstanceOP2y ago
thanks that's what I did in the examples above
Want results from more Discord servers?
Add your server