
Passo 1 de 7
Uma chamada, comportamentos diferentes
Entenda como a mesma chamada de método pode executar implementações diferentes conforme o objeto que recebe a chamada.
Trilha de aprendizado · Nível 7 · Tutorial 6
Escrever código que trabalha com diferentes objetos por meio de operações compatíveis, sem depender de verificações de tipo para cada implementação.
Uma chamada, comportamentos diferentes
Entenda como a mesma chamada de método pode executar implementações diferentes conforme o objeto que recebe a chamada. 2 min
Compatibilidade sem herança obrigatória
Criar uma classe independente que participa da mesma chamada dos formatadores existentes por oferecer o comportamento esperado. 2 min
Definir o que a operação promete
Descreva as expectativas que tornam diferentes formatadores compatíveis com a mesma chamada. 3 min
Escrever um consumidor sem decisões por tipo
Crie uma função consumidora que chama a operação compatível dos formatadores sem escolher uma implementação por classe. 3 min
Trocar o colaborador sem mudar quem o utiliza
Configure o mesmo PainelDeAvisos com formatadores diferentes, preservando o código que delega a formatação e registra o histórico. 2 min
Reconhecer métodos que não são substituíveis
Diagnostique por que objetos com um método de mesmo nome ainda podem ser incompatíveis com a chamada e com o comportamento esperado pelo consumidor. 3 min
Aplicar e revisar a substituição por comportamento
Implemente um formatador independente, use-o em dois consumidores existentes e verifique sua compatibilidade sem alterar o código desses consumidores. 4 min

Passo 1 de 7
Entenda como a mesma chamada de método pode executar implementações diferentes conforme o objeto que recebe a chamada.
Polimorfismo permite usar da mesma maneira objetos que oferecem comportamentos compatíveis. No exemplo, os dois objetos respondem à chamada formatar(texto), mas cada um apresenta o texto de um jeito.
A subclasse aproveita a sobrescrita que você já conhece: ela redefine formatar sem mudar a forma de chamar o método.
Execute este código e compare os resultados.
class FormatadorSimples:
def formatar(self, texto):
return texto
class FormatadorEntreColchetes(FormatadorSimples):
def formatar(self, texto):
return f"[{texto}]"
simples = FormatadorSimples()
com_colchetes = FormatadorEntreColchetes()
print(simples.formatar("Alerta"))
print(com_colchetes.formatar("Alerta"))
# Saída:
# Alerta
# [Alerta]Nas duas chamadas, o nome do método e o argumento são iguais. Python procura a implementação adequada conforme o objeto que está antes do ponto.
Se o objeto é FormatadorSimples, executa a implementação da classe base. Se é FormatadorEntreColchetes, executa a implementação sobrescrita. O nome da variável não determina essa escolha.
![Diagrama dividido em dois caminhos: a mesma chamada formatar com o texto Alerta chega ao formatador simples e produz Alerta; no outro caminho, chega ao formatador entre colchetes e produz [Alerta].](https://cursa-json-routes.s3.amazonaws.com/learning-paths/pt/python/b730454e0225/level-07/tutorial-06/step-01/screen-02/item-02.webp)
A chamada tem a mesma forma; o objeto receptor define qual implementação será executada.
Dica
Para prever qual método será executado, identifique o objeto que recebe a chamada — a expressão à esquerda do ponto.
Considere as classes anteriores:
formatador = FormatadorEntreColchetes()
resultado = formatador.formatar("Aviso")Qual valor será armazenado em resultado?

Passo 2 de 7
Criar uma classe independente que participa da mesma chamada dos formatadores existentes por oferecer o comportamento esperado.
No step anterior, objetos diferentes responderam à chamada formatar(texto). Eles podem participar desse uso polimórfico mesmo sem pertencer à mesma hierarquia.
Em Python, o código pode usar as operações que um objeto oferece, em vez de exigir uma classe concreta. Essa abordagem é conhecida como duck typing: se o objeto oferece o comportamento necessário para aquele uso, ele pode ser utilizado.
À esquerda, uma subclasse recebe a operação de uma classe base. À direita, objetos sem relação de herança chegam ao mesmo ponto de uso porque todos oferecem uma operação compatível.

A herança é uma forma de compartilhar uma operação, mas não é requisito para usar objetos de modo polimórfico.
FormatadorComPrefixo não herda de FormatadorSimples. Ainda assim, sua instância recebe a mesma chamada porque também define formatar(texto).
class FormatadorSimples:
def formatar(self, texto):
return texto
class FormatadorEntreColchetes(FormatadorSimples):
def formatar(self, texto):
return f"[{texto}]"
class FormatadorComPrefixo:
def __init__(self, prefixo):
self.prefixo = prefixo
def formatar(self, texto):
return f"{self.prefixo}{texto}"
simples = FormatadorSimples()
entre_colchetes = FormatadorEntreColchetes()
com_prefixo = FormatadorComPrefixo("AVISO: ")
print(simples.formatar("Manutenção"))
print(entre_colchetes.formatar("Manutenção"))
print(com_prefixo.formatar("Manutenção"))
# Saída:
# Manutenção
# [Manutenção]
# AVISO: ManutençãoDica
As três últimas chamadas têm a mesma forma: objeto.formatar("Manutenção"). Quem chama não precisa saber se o método foi herdado ou definido diretamente pela classe do objeto.
Considere o método abaixo:
def formatar(self, texto):
Complete a instrução para que FormatadorComPrefixo("# ").formatar("Alerta") devolva # Alerta:
return ____

Passo 3 de 7
Descreva as expectativas que tornam diferentes formatadores compatíveis com a mesma chamada.
Para decidir se um objeto pode ser usado como formatador, precisamos explicitar o que o código consumidor espera de formatar. Esse contrato informal descreve quatro dimensões:
O contrato é informal porque está registrado em linguagem natural, não porque seja opcional.

A chamada fornece uma string e recebe outra string que ainda contém a mensagem original. Imprimir ou alterar o estado do formatador não faz parte da operação.
Exemplo
formatar(texto) recebe uma string como argumento posicional e devolve uma string. O resultado preserva o texto original sem modificá-lo, embora possa acrescentar prefixos, sufixos ou outros elementos decorativos. A operação apenas produz e retorna o resultado: não imprime mensagens nem altera o estado do formatador.
A docstring registra a expectativa perto da operação, sem exigir ferramentas de tipagem.
class FormatadorSimples:
def formatar(self, texto):
"""Formata uma string e devolve outra string.
O resultado preserva o texto original e pode acrescentar
decoração. O método não imprime mensagens nem altera o
estado do formatador.
"""
return textoDica
As assinaturas não precisam ser textualmente idênticas. Um método pode ter um parâmetro opcional adicional e ainda aceitar a chamada formatador.formatar(texto). O essencial é atender à chamada e às expectativas de resultado, significado e efeitos.
Relacione cada dimensão do contrato de formatar à expectativa correspondente.
Toque em um item e depois no par correspondente.

Passo 4 de 7
Crie uma função consumidora que chama a operação compatível dos formatadores sem escolher uma implementação por classe.
Código consumidor é aquele que utiliza a operação pública oferecida por outro objeto. Neste caso, ele recebe um formatador e precisa usar formatar(texto).
Se o consumidor verifica a classe para decidir como montar cada resultado, ele assume responsabilidades que pertencem aos formatadores. Além disso, cada nova implementação exige alterar essa cadeia de decisões.
Este código conhece cada implementação e reproduz suas diferenças:
def formatar_mensagens(formatador, textos):
resultados = []
for texto in textos:
if isinstance(formatador, FormatadorEntreColchetes):
resultados.append(f"[{texto}]")
elif isinstance(formatador, FormatadorComPrefixo):
resultados.append(f"{formatador.prefixo}{texto}")
else:
resultados.append(texto)
return resultadosO consumidor não precisa saber como cada classe formata. Ele apenas faz a chamada prevista pelo contrato. Durante a execução, o próprio objeto recebido determina qual implementação de formatar será usada.
À esquerda, o fluxo se divide conforme a classe. À direita, todas as mensagens seguem pela mesma chamada, enquanto cada objeto produz seu próprio resultado.

No fluxo polimórfico, o consumidor conhece a operação esperada, não a lista de classes que podem realizá-la.
A função percorre os textos, chama a mesma operação em cada item e devolve a lista de resultados:
def formatar_mensagens(formatador, textos):
resultados = []
for texto in textos:
resultado = formatador.formatar(texto)
resultados.append(resultado)
return resultados
mensagens = ["Sistema iniciado", "Atualização disponível"]
print(formatar_mensagens(FormatadorSimples(), mensagens))
print(formatar_mensagens(FormatadorEntreColchetes(), mensagens))
print(formatar_mensagens(FormatadorComPrefixo("AVISO: "), mensagens))Dica
Uma verificação pode continuar sendo adequada para validar uma entrada. Por exemplo, a função poderia rejeitar um valor que não atende à regra definida para textos. O problema é usar type ou isinstance para selecionar como cada classe deve formatar.
Exemplo
Validar: “textos atende às regras de entrada da função?”
Selecionar por tipo: “Se o formatador pertence à classe A, faça isto; se pertence à classe B, faça aquilo.”
É essa segunda decisão que a chamada uniforme elimina. Um objeto que atende ao contrato de formatar(texto) pode participar sem ganhar um ramo próprio.
Reescreva esta função sem isinstance, preservando o processamento de todos os textos:
def preparar_avisos(formatador, textos):
if isinstance(formatador, FormatadorSimples):
return [formatador.formatar(texto) for texto in textos]
elif isinstance(formatador, FormatadorEntreColchetes):
return [formatador.formatar(texto) for texto in textos]
elif isinstance(formatador, FormatadorComPrefixo):
return [formatador.formatar(texto) for texto in textos]
return []Refatore a função e explique, em uma ou duas frases, por que uma nova classe compatível não precisa ser adicionada a uma cadeia de elif.
Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 7
Configure o mesmo PainelDeAvisos com formatadores diferentes, preservando o código que delega a formatação e registra o histórico.
O PainelDeAvisos mantém seu próprio histórico, mas entrega a formatação a um objeto recebido no __init__. Como os formatadores oferecem a operação compatível formatar(texto), o painel não precisa saber qual classe recebeu: ele apenas chama o método, obtém o resultado e o registra.

O código do PainelDeAvisos permanece igual. Apenas o colaborador fornecido na criação muda: um resultado pode ser [Manutenção às 18h]; o outro, URGENTE: Manutenção às 18h.
O painel consome apenas formatar(texto). Os detalhes de apresentação ficam nos formatadores.
class FormatadorSimples:
def formatar(self, texto):
return texto
class FormatadorEntreColchetes(FormatadorSimples):
def formatar(self, texto):
return f"[{texto}]"
class FormatadorComPrefixo:
def __init__(self, prefixo):
self.prefixo = prefixo
def formatar(self, texto):
return f"{self.prefixo}: {texto}"
class PainelDeAvisos:
def __init__(self, formatador):
self.formatador = formatador
self.historico = []
def registrar_aviso(self, mensagem):
mensagem_formatada = self.formatador.formatar(mensagem)
self.historico.append(mensagem_formatada)
return mensagem_formatadaDica
O formatador decide a apresentação da mensagem. O painel decide quando registrar o aviso e mantém seu próprio histórico. Trocar o colaborador não deve transferir essas responsabilidades nem exigir uma decisão por tipo dentro de registrar_aviso.
A escolha do comportamento ocorre na montagem dos objetos. O método registrar_aviso não muda.
painel_interno = PainelDeAvisos(
FormatadorEntreColchetes()
)
painel_urgente = PainelDeAvisos(
FormatadorComPrefixo("URGENTE")
)
print(painel_interno.registrar_aviso("Manutenção às 18h"))
print(painel_urgente.registrar_aviso("Manutenção às 18h"))
print(painel_interno.historico)
print(painel_urgente.historico)Exemplo
[Manutenção às 18h]
URGENTE: Manutenção às 18h
['[Manutenção às 18h]']
['URGENTE: Manutenção às 18h']Cada painel possui seu próprio histórico. A diferença de apresentação vem do colaborador escolhido na criação.
Para usar um prefixo no lugar de colchetes, é necessário adicionar um if por tipo dentro de PainelDeAvisos.registrar_aviso.

Passo 6 de 7
Diagnostique por que objetos com um método de mesmo nome ainda podem ser incompatíveis com a chamada e com o comportamento esperado pelo consumidor.
Para substituir um formatador, não basta que o objeto tenha um método chamado formatar. O método precisa aceitar a chamada feita pelo consumidor, devolver o resultado esperado, preservar o significado da operação e evitar efeitos não previstos.
Nosso consumidor chama formatar(texto) e espera receber uma string que preserve o texto original, sem impressão nem alteração de estado no formatador.
Um objeto pode falhar ao receber a chamada, produzir um resultado inadequado, realizar outra operação ou provocar um efeito inesperado.

A presença de formatar não garante compatibilidade de argumentos, resultado, significado ou efeitos.
Compare as classes abaixo. EstiloOpcional continua aceitando formatar(texto) porque seu argumento adicional tem valor padrão. As demais classes possuem o nome esperado, mas violam pelo menos uma parte do contrato.
Observe argumentos, retorno, significado e alterações de estado.
class ExigeEstilo:
def formatar(self, texto, estilo):
return f"{estilo}{texto}{estilo}"
class EstiloOpcional:
def formatar(self, texto, estilo="*"):
return f"{estilo}{texto}{estilo}"
class ApenasImprime:
def formatar(self, texto):
print(texto)
# Sem return explícito: o resultado é None.
class MedeTexto:
def formatar(self, texto):
return f"{len(texto)} caracteres"
class ContaChamadas:
def __init__(self):
self.chamadas = 0
def formatar(self, texto):
self.chamadas += 1
return f"[{texto}]"Relacione cada implementação à forma como ela deixa de atender ao contrato.
Toque em um item e depois no par correspondente.
Atenção
A falta de um argumento pode produzir um TypeError imediatamente. Já MedeTexto e ContaChamadas aceitam a chamada e podem terminar sem exceção, embora violem o significado ou os efeitos previstos. Não capture erros indiscriminadamente para esconder a incompatibilidade: corrija a implementação ou escolha um colaborador compatível.
Resumo
None?
Passo 7 de 7
Implemente um formatador independente, use-o em dois consumidores existentes e verifique sua compatibilidade sem alterar o código desses consumidores.
Nesta prática, você criará FormatadorComMarcadores, uma classe independente que envolve cada texto com marcadores configuráveis.
Ela será usada por formatar_mensagens e por PainelDeAvisos. Esses consumidores continuarão chamando apenas formatar(texto): nenhuma decisão baseada na classe será adicionada a eles.
O mesmo objeto pode colaborar com consumidores diferentes quando oferece a operação esperada por ambos.

A substituição acontece na configuração e nas chamadas externas; o código interno dos consumidores permanece igual.
Crie um arquivo .py no seu computador e copie o código abaixo. Complete somente os dois trechos indicados na classe FormatadorComMarcadores.
O método formatar deve aceitar uma string por posição, devolver outra string com os marcadores e preservar integralmente o texto recebido. Ele não deve imprimir nada nem modificar a configuração do objeto.
Execute com Python 3 depois de completar a nova classe. Não altere formatar_mensagens nem PainelDeAvisos.
class FormatadorSimples:
def formatar(self, texto):
return texto
class FormatadorEntreColchetes(FormatadorSimples):
def formatar(self, texto):
return f"[{texto}]"
class FormatadorComPrefixo:
def __init__(self, prefixo):
self.prefixo = prefixo
def formatar(self, texto):
return f"{self.prefixo}{texto}"
class FormatadorComMarcadores:
def __init__(self, inicio, fim):
# Substitua o pass: guarde os dois marcadores no objeto.
pass
def formatar(self, texto):
# Substitua o pass: devolva inicio + texto + fim.
pass
def formatar_mensagens(formatador, textos):
return [formatador.formatar(texto) for texto in textos]
class PainelDeAvisos:
def __init__(self, formatador):
self.formatador = formatador
self.historico = []
def registrar(self, mensagem):
mensagem_formatada = self.formatador.formatar(mensagem)
self.historico.append(mensagem_formatada)
return mensagem_formatada
# Configuração com o novo colaborador
formatador = FormatadorComMarcadores("<", ">")
configuracao_antes = (formatador.inicio, formatador.fim)
# Consumidor 1: função
resultados = formatar_mensagens(
formatador,
["Atenção", "Reunião às 14h"],
)
# Consumidor 2: objeto composto
painel = PainelDeAvisos(formatador)
registrado = painel.registrar("Servidor reiniciado")
print(resultados)
print(registrado)
print(painel.historico)
print(configuracao_antes == (formatador.inicio, formatador.fim))Exemplo
A execução deve apresentar exatamente estas quatro linhas, sem mensagens extras:
['<Atenção>', '<Reunião às 14h>']<Servidor reiniciado>['<Servidor reiniciado>']True
As três primeiras linhas confirmam o retorno textual e a preservação das mensagens. A última confirma que os marcadores não foram modificados durante a formatação. Se surgirem linhas extras, verifique se você colocou algum print dentro do formatador.
Antes de responder, confira quatro pontos: formatar(texto) aceita a chamada usada pelos consumidores; o resultado é uma string; o conteúdo original permanece no resultado; e a operação não imprime nem altera os marcadores.
Observe também que FormatadorComMarcadores não precisa herdar de outro formatador. A compatibilidade vem do comportamento oferecido, não de uma classe base comum.
Apresente sua implementação de FormatadorComMarcadores. Depois, relate o que apareceu ao usar o objeto em formatar_mensagens e em PainelDeAvisos. Explique por que nenhum dos dois consumidores precisou ser modificado.
Escreva pelo menos 180 caracteres (0/180).
Resumo
Um objeto pode substituir outro quando atende às chamadas e às expectativas relevantes do consumidor.
type ou isinstance.Parabéns! Você concluiu: Aplicar polimorfismo pelo comportamento dos objetos
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks