Desafio Python
Composição vs herança
Enunciado
Implemente um sistema de log que pode ser usado por uma classe Servico. Em vez de herdar de Logger, Servico deve compor com um logger injetado. Crie Logger (com log(msg)) e Servico que recebe um logger no construtor e usa self.logger.log() em um método executar(). Mostre que é possível trocar o logger sem alterar Servico.
Requisitos
Logger.log(msg)imprime"[LOG] msg".Servico.__init__recebe um objeto logger e armazena emself.logger.Servico.executar()chamaself.logger.log("Executando serviço").- Crie dois loggers diferentes (ex.:
Loggere umLoggerColoridoque adiciona"[COR]") e use o mesmoServicocom cada um.
Código inicial
class Logger:
def log(self, msg):
# complete
pass
class LoggerColorido:
def log(self, msg):
# complete
pass
class Servico:
def __init__(self, logger):
# complete
pass
def executar(self):
# complete
pass
# teste
s1 = Servico(Logger())
s1.executar()
s2 = Servico(LoggerColorido())
s2.executar()
Saída esperada
[LOG] Executando serviço
[COR] Executando serviço
Ver dica
Composição significa que Servico tem um logger, não é um logger. Basta chamar self.logger.log(...).
Mostrar solução
class Logger:
def log(self, msg):
print(f"[LOG] {msg}")
class LoggerColorido:
def log(self, msg):
print(f"[COR] {msg}")
class Servico:
def __init__(self, logger):
# composição: Servico TEM um logger
self.logger = logger
def executar(self):
self.logger.log("Executando serviço")
if __name__ == "__main__":
# mesmo Servico, loggers diferentes
s1 = Servico(Logger())
s1.executar()
s2 = Servico(LoggerColorido())
s2.executar()
Passo a passo
Loggerdefinelog(msg)que imprime[LOG] msg.LoggerColoridotambém definelog(msg), mas imprime[COR] msg. Não herda deLogger, apenas implementa a mesma interface (duck typing).Servico.__init__recebeloggere guarda emself.logger. Isso é composição:Servicotem um logger.Servico.executar()chamaself.logger.log("Executando serviço"), delegando o trabalho ao objeto logger.- No bloco principal, criamos dois
Servicocom loggers diferentes e chamamosexecutar(). O comportamento muda sem alterarServico.
Por que funciona
Composição permite trocar a implementação do logger sem modificar Servico, seguindo o princípio da inversão de dependência. Se usássemos herança (class Servico(Logger)), Servico ficaria acoplado a uma única implementação e não poderia usar LoggerColorido simultaneamente. A composição favorece flexibilidade e testabilidade.
Erros comuns
- Herdar de
Loggerem vez de compor:class Servico(Logger):limita a um logger e mistura responsabilidades. - Não passar o logger no construtor:
self.loggerfica indefinido eexecutar()falha comAttributeError. - Chamar
Logger.log(msg)diretamente em vez deself.logger.log(msg): ignora a instância injetada e quebra a composição.
Outra forma de resolver
Poderíamos usar uma classe base abstrata LoggerBase com log abstrato e fazer Logger e LoggerColorido herdarem dela. Isso é útil quando queremos garantir que todos os loggers implementem log, mas adiciona complexidade. Para este caso, duck typing é suficiente.
Saída esperada
[LOG] Executando serviço
[COR] Executando serviço