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 em self.logger.
  • Servico.executar() chama self.logger.log("Executando serviço").
  • Crie dois loggers diferentes (ex.: Logger e um LoggerColorido que adiciona "[COR]") e use o mesmo Servico com 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

  1. Logger define log(msg) que imprime [LOG] msg.
  2. LoggerColorido também define log(msg), mas imprime [COR] msg. Não herda de Logger, apenas implementa a mesma interface (duck typing).
  3. Servico.__init__ recebe logger e guarda em self.logger. Isso é composição: Servico tem um logger.
  4. Servico.executar() chama self.logger.log("Executando serviço"), delegando o trabalho ao objeto logger.
  5. No bloco principal, criamos dois Servico com loggers diferentes e chamamos executar(). O comportamento muda sem alterar Servico.

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 Logger em vez de compor: class Servico(Logger): limita a um logger e mistura responsabilidades.
  • Não passar o logger no construtor: self.logger fica indefinido e executar() falha com AttributeError.
  • Chamar Logger.log(msg) diretamente em vez de self.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