Trilha de aprendizado · Nível 7 · Tutorial 4

Compor objetos com responsabilidades distintas

Distribuir responsabilidades entre objetos colaboradores e implementar uma classe que delega parte de suas operações a outros objetos.

  • Nível: Intermediário
  • Duração: 18 min
  • 6 passos
Compor objetos com responsabilidades distintas

O que você vai percorrer

  1. Separar as responsabilidades de um atendimento Identifique quais estados, regras e operações pertencem à fila e quais pertencem ao guichê em uma simulação de atendimento. 2 min
  2. Conectar objetos pela inicialização Ligue um Guiche a uma FilaAtendimento recebendo e armazenando a instância colaboradora durante a inicialização. 3 min
  3. Delegar sem assumir as regras do colaborador Implemente o fluxo de atendimento fazendo o guichê solicitar a próxima pessoa pela interface pública da fila, sem duplicar suas regras ou acessar seus dados internos. 4 min
  4. Decidir entre um colaborador exclusivo e compartilhado Compare configurações em que dois guichês usam a mesma fila ou filas independentes e preveja os efeitos sobre o estado de cada objeto. 3 min
  5. Reconhecer quando uma função é suficiente Compare uma classe colaboradora com uma função e escolha conforme a necessidade de manter estado e regras entre operações. 2 min
  6. Aplicar e revisar a composição Integre fila, guichê e função de formatação em uma simulação executável e revise as decisões de composição. 5 min

O que você vai aprender

  • Identificar responsabilidades distintas que não devem ficar concentradas em uma única classe.
  • Representar uma relação de colaboração armazenando outro objeto em um atributo.
  • Receber um colaborador na inicialização e delegar operações a ele.
  • Justificar o uso de uma classe colaboradora ou de uma função conforme a necessidade de estado próprio.

Antes de começar

  • Preservar regras de validade em objetos
  • Decompor um programa em funções com responsabilidades claras
  • Controlar referências e cópias de coleções

Passo 1 de 6

Separar as responsabilidades de um atendimento

Identifique quais estados, regras e operações pertencem à fila e quais pertencem ao guichê em uma simulação de atendimento.

Um cenário, duas responsabilidades

A simulação de atendimento

Imagine uma simulação em memória: pessoas fictícias entram em uma fila e são chamadas por guichês. Uma única classe até poderia guardar a lista de pessoas, decidir quem sai primeiro, identificar o guichê e contar seus atendimentos. Porém, ela estaria controlando dois estados com regras diferentes.

Exemplo

Sinais de concentração

Se a mesma classe responde a perguntas como “quem é a próxima pessoa?” e “quantos atendimentos este guichê realizou?”, há um sinal de responsabilidades concentradas. A primeira pergunta trata da fila; a segunda, do guichê.

A fronteira entre fila e guichê

Separe pelo estado e pelas regras

A FilaAtendimento cuida das pessoas aguardando, preserva a ordem de entrada e retira a próxima pessoa. O Guiche mantém sua identificação, registra seu total de atendimentos e coordena cada atendimento. A separação não depende do número de linhas: mesmo uma classe curta pode misturar responsabilidades distintas.

Dois conjuntos de estado e comportamento

Observe que cada objeto concentra os dados e as operações relacionados à própria responsabilidade.

Diagrama com uma fila de pessoas e suas operações em uma área, e um guichê com identificação e contador de atendimentos em outra área.

A fila controla quem aguarda e a ordem; o guichê controla sua identidade e seu total de atendimentos.

Classifique as responsabilidades

FilaAtendimento ou Guiche?

Associe cada responsabilidade à classe adequada.

Toque em um item e depois no par correspondente.

Passo 2 de 6

Conectar objetos pela inicialização

Ligue um Guiche a uma FilaAtendimento recebendo e armazenando a instância colaboradora durante a inicialização.

Um objeto usa o outro

Composição por colaboração

Na composição, um objeto organiza seu trabalho com a ajuda de outros objetos. Aqui, o Guiche usa uma FilaAtendimento: a fila conserva as pessoas aguardando, enquanto o guichê mantém sua identificação e seu total de atendimentos.

Essa ligação não transforma o guichê em um tipo de fila. São objetos com responsabilidades distintas, conectados por uma referência.

A referência entre os objetos

O atributo fila do objeto Guiche aponta para o objeto FilaAtendimento. A fila não é copiada para dentro do guichê.

Diagrama com um objeto Guiche ligado por seu atributo fila a um objeto FilaAtendimento separado.

O guichê mantém uma referência para a fila colaboradora; cada objeto continua responsável pelo próprio estado.

Receber e guardar o colaborador

A ligação acontece em __init__

Primeiro, crie a instância de FilaAtendimento. Depois, passe esse objeto ao inicializar o Guiche. O parâmetro fila recebe a referência, e self.fila = fila a conserva em um atributo de instância.

Inicialização explícita

Neste exemplo, o guichê recebe a fila pronta em vez de criar ou copiar seus dados.

python
class FilaAtendimento:
    def __init__(self):
        self._pessoas = []


class Guiche:
    def __init__(self, identificacao, fila):
        self.identificacao = identificacao
        self.total_atendimentos = 0
        self.fila = fila


fila_principal = FilaAtendimento()
guiche_1 = Guiche("G1", fila_principal)

Dica

Objeto, não classe nem dados copiados

O argumento é fila_principal, uma instância já criada. Passar FilaAtendimento sem parênteses entregaria a própria classe. Passar fila_principal._pessoas entregaria apenas a coleção interna, eliminando a colaboração com o objeto responsável pelas regras da fila.

Complete a inicialização

Armazene a referência

Complete a última linha para guardar no guichê exatamente a instância recebida:

class Guiche:
def init(self, identificacao, fila):
self.identificacao = identificacao
self.total_atendimentos = 0
self.fila = _

Identifique a conexão correta

Qual código passa o colaborador?

Considerando que Guiche.__init__ recebe os parâmetros identificacao e fila, qual opção estabelece corretamente a colaboração explícita?

Passo 3 de 6

Delegar sem assumir as regras do colaborador

Implemente o fluxo de atendimento fazendo o guichê solicitar a próxima pessoa pela interface pública da fila, sem duplicar suas regras ou acessar seus dados internos.

Cada objeto cuida de suas próprias regras

Delegar é pedir, não fazer no lugar

O Guiche coordena o atendimento, mas não decide como retirar alguém da fila. Em atender_proximo, ele chama o método público da FilaAtendimento armazenada em self.fila.

A fila continua responsável por preservar a ordem de entrada e rejeitar uma retirada quando não há ninguém aguardando. O guichê registra somente um atendimento que realmente começou.

Fluxo de um atendimento delegado

Diagrama em que um guichê solicita a próxima pessoa à fila, recebe uma pessoa e só então aumenta seu total de atendimentos.

A retirada acontece na fila; o total só é atualizado no guichê depois que uma pessoa é devolvida com sucesso.

Implementar pela interface pública

Fila retira; guichê coordena

Observe a ordem das instruções em atender_proximo: primeiro ocorre a retirada; somente depois o total é incrementado.

python
class FilaAtendimento:
    def __init__(self):
        self._pessoas = []

    def entrar(self, pessoa):
        self._pessoas.append(pessoa)

    def retirar_proxima(self):
        if not self._pessoas:
            raise ValueError("Não há pessoas aguardando")

        return self._pessoas.pop(0)


class Guiche:
    def __init__(self, identificacao, fila):
        self.identificacao = identificacao
        self.fila = fila
        self.total_atendimentos = 0

    def atender_proximo(self):
        pessoa = self.fila.retirar_proxima()
        self.total_atendimentos += 1
        return pessoa


fila = FilaAtendimento()
fila.entrar("Ana")
fila.entrar("Bruno")

guiche = Guiche("G1", fila)

print(guiche.atender_proximo())
print(guiche.total_atendimentos)

Dica

A posição do incremento importa

Se retirar_proxima() rejeitar a operação, a execução não chega a self.total_atendimentos += 1. Assim, uma tentativa com a fila vazia não é registrada como atendimento realizado.

Atenção

Não invada o estado da fila

O Guiche não deve usar self.fila._pessoas, aplicar pop(0) nem repetir a verificação de fila vazia. Isso duplicaria no guichê os dados, a ordem de retirada e as validações que pertencem à FilaAtendimento.

Ordene o fluxo delegado

Do pedido ao resultado

Coloque as ações de atender_proximo na ordem correta.

  1. Solicitar a próxima pessoa com `self.fila.retirar_proxima()`.
  2. Incrementar `self.total_atendimentos`.
  3. Receber a pessoa devolvida pela fila.
  4. Devolver a pessoa atendida.

Localize a invasão de responsabilidade

Uma implementação problemática

Analise esta versão de atender_proximo.

python
def atender_proximo(self):
    self.total_atendimentos += 1
    return self.fila._pessoas.pop(0)

Explique os dois problemas

Por que essa implementação invade a responsabilidade da fila? Explique também o que acontece com total_atendimentos quando a fila está vazia.

Escreva pelo menos 60 caracteres (0/60).

Passo 4 de 6

Decidir entre um colaborador exclusivo e compartilhado

Compare configurações em que dois guichês usam a mesma fila ou filas independentes e preveja os efeitos sobre o estado de cada objeto.

Uma fila comum ou filas independentes?

A instância passada define o compartilhamento

Dois guichês podem receber a mesma instância de FilaAtendimento ou instâncias diferentes.

  • Colaborador compartilhado: os dois guichês consultam e alteram uma única fila. Uma pessoa retirada por um deles deixa de estar disponível para o outro.
  • Colaborador exclusivo: cada guichê recebe sua própria fila. Retirar alguém de uma fila não modifica a outra.

Essa escolha deve representar o funcionamento do domínio. Compartilhar um colaborador não é um erro por definição.

Comparação das ligações

Comparação entre dois guichês ligados à mesma fila e dois guichês ligados a filas distintas.

À esquerda, duas referências chegam ao mesmo objeto de fila. À direita, cada referência chega a um objeto de fila diferente.

Passar a mesma fila aos dois guichês

O estado da fila é compartilhado; os totais não

No código a seguir, guiche_1 e guiche_2 guardam uma referência para o mesmo objeto fila_comum. As chamadas são sequenciais: primeiro o guichê 1 retira uma pessoa; depois, o guichê 2 encontra a fila já modificada.

O atributo total_atendimentos, porém, pertence a cada instância de Guiche. Por isso, os totais continuam separados.

Configuração com fila compartilhada

Considere as classes implementadas nos steps anteriores: a fila controla a ordem e o guichê delega a retirada.

python
fila_comum = FilaAtendimento(["Ana", "Beto", "Caio"])

guiche_1 = Guiche("G1", fila_comum)
guiche_2 = Guiche("G2", fila_comum)

print(guiche_1.atender_proximo())  # Ana
print(guiche_2.atender_proximo())  # Beto

print(fila_comum.pessoas_aguardando())  # ('Caio',)
print(guiche_1.total_atendimentos)       # 1
print(guiche_2.total_atendimentos)       # 1

Dica

Observe a identidade, não o nome da variável

O compartilhamento ocorre porque a mesma instância, fila_comum, foi passada aos dois inicializadores. Criar duas variáveis apontando para esse objeto não criaria duas filas.

Criar uma fila para cada guichê

Instâncias distintas mantêm estados independentes

Quando cada guichê deve atender seu próprio grupo de pessoas, crie duas instâncias de FilaAtendimento e passe uma para cada guichê. Agora, cada retirada afeta somente a fila correspondente.

Configuração com filas exclusivas

As duas filas podem conter dados diferentes e avançar em ritmos diferentes.

python
fila_1 = FilaAtendimento(["Ana", "Beto"])
fila_2 = FilaAtendimento(["Caio", "Dora"])

guiche_1 = Guiche("G1", fila_1)
guiche_2 = Guiche("G2", fila_2)

print(guiche_1.atender_proximo())  # Ana
print(guiche_2.atender_proximo())  # Caio
print(guiche_1.atender_proximo())  # Beto

print(fila_1.pessoas_aguardando())  # ()
print(fila_2.pessoas_aguardando())  # ('Dora',)
print(guiche_1.total_atendimentos)  # 2
print(guiche_2.total_atendimentos)  # 1

Preveja e escolha a configuração

Duas chamadas em uma fila compartilhada

Uma fila contém Lia, Noé e Ivo, nessa ordem. G1 e G2 recebem essa mesma instância. Primeiro, G2.atender_proximo() é chamado; depois, G1.atender_proximo().

Qual é o estado após as duas chamadas?

Escolha conforme o domínio

Uma empresa possui duas unidades físicas, Norte e Sul. Cada unidade distribui suas próprias senhas, e uma pessoa atendida no Norte não deve sair da fila do Sul. Você usaria uma fila compartilhada ou filas exclusivas? Justifique com base no estado que será alterado.

Escreva pelo menos 50 caracteres (0/50).

Passo 5 de 6

Reconhecer quando uma função é suficiente

Compare uma classe colaboradora com uma função e escolha conforme a necessidade de manter estado e regras entre operações.

Estado persistente ou transformação pontual?

O estado orienta a escolha

Uma classe colaboradora é útil quando precisa manter estado entre operações e proteger regras relacionadas a ele. FilaAtendimento, por exemplo, conserva as pessoas que ainda aguardam e controla a ordem de retirada.

Já uma operação que recebe todos os dados necessários, produz um resultado e não precisa guardar nada para a próxima chamada costuma ser uma boa candidata a função. O critério principal não é o tamanho do código, mas a existência de estado próprio persistente.

Duas necessidades diferentes

À esquerda, várias operações consultam e alteram o mesmo estado da fila. À direita, cada formatação depende apenas dos dados recebidos naquela chamada.

Comparação entre uma fila que mantém pessoas entre operações e uma transformação pontual de dados em uma mensagem.

A fila precisa conservar estado; a formatação pode apenas transformar entradas em um texto.

Uma função para formatar a mensagem

Receber, transformar e retornar

A mensagem de atendimento pode receber o identificador do guichê, o nome da pessoa e o total já atendido. Como todos os dados chegam por parâmetros, a operação não precisa de estado próprio nem de uma nova classe.

Formatação sem estado próprio

Cada chamada é independente: a função usa somente os argumentos recebidos e retorna uma nova string.

python
def formatar_atendimento(guiche, pessoa, total_atendimentos):
    return (
        f"Guichê {guiche} atendeu {pessoa}. "
        f"Total do guichê: {total_atendimentos}."
    )


mensagem = formatar_atendimento(
    guiche="A",
    pessoa="Lia",
    total_atendimentos=3,
)

print(mensagem)
# Guichê A atendeu Lia. Total do guichê: 3.

Dica

Um critério, não uma proibição

A ausência de estado próprio favorece o uso de uma função, mas não estabelece que toda classe sem estado esteja errada. A decisão depende do papel da operação no programa. Neste cenário, criar uma classe apenas para abrigar a formatação não acrescentaria uma responsabilidade persistente.

Classifique pelas responsabilidades

Função ou classe colaboradora?

Associe cada operação à alternativa de modelagem mais adequada neste cenário.

Toque em um item e depois no par correspondente.

Justifique a escolha

Explique pelo estado

Uma operação recebe o identificador do guichê, o nome da pessoa e o total de atendimentos, então retorna uma mensagem. Ela não mantém histórico nem configuração entre chamadas. Você escolheria uma função ou uma classe colaboradora? Justifique e indique que tipo de mudança poderia tornar uma classe mais útil.

Escreva pelo menos 60 caracteres (0/60).

Passo 6 de 6

Aplicar e revisar a composição

Integre fila, guichê e função de formatação em uma simulação executável e revise as decisões de composição.

Mapa da solução integrada

Três responsabilidades, uma colaboração

A simulação será formada apenas pelos elementos já estudados:

  • FilaAtendimento mantém as pessoas, preserva a ordem e rejeita retiradas quando está vazia.
  • Guiche mantém sua identificação e seu total de atendimentos. Para atender, delega a retirada à fila recebida em __init__.
  • formatar_atendimento recebe os dados necessários e devolve uma mensagem. Como não precisa manter estado entre chamadas, continua sendo uma função.

A mesma implementação permite conectar dois guichês a uma fila comum ou entregar uma fila exclusiva a cada um.

Duas configurações possíveis

Compare as referências entre os objetos. Na configuração compartilhada, os dois guichês apontam para a mesma fila. Na exclusiva, cada guichê aponta para uma fila diferente.

Comparação entre dois guichês conectados à mesma fila e dois guichês conectados a filas distintas.

Compartilhar ou não a fila é uma decisão explícita feita ao criar os guichês.

Execute a simulação completa

Prática no seu computador

Crie um arquivo chamado atendimento.py, copie todo o código abaixo e execute-o com python atendimento.py. Observe primeiro a fila compartilhada e depois as filas exclusivas. Se quiser repetir o experimento, troque apenas os nomes fictícios, sem alterar a ligação entre os objetos.

Simulação em um único arquivo

O código usa somente a interface pública da fila para adicionar e retirar pessoas.

python
class FilaAtendimento:
    def __init__(self):
        self._pessoas = []

    def entrar(self, pessoa):
        self._pessoas.append(pessoa)

    def retirar_proxima(self):
        if not self._pessoas:
            raise ValueError("Não há pessoas aguardando")

        return self._pessoas.pop(0)


class Guiche:
    def __init__(self, identificacao, fila):
        self.identificacao = identificacao
        self.total_atendimentos = 0
        self._fila = fila

    def atender_proximo(self):
        pessoa = self._fila.retirar_proxima()
        self.total_atendimentos += 1
        return pessoa


def formatar_atendimento(identificacao, pessoa):
    return f"Guichê {identificacao} atendeu {pessoa}"


print("=== FILA COMPARTILHADA ===")
fila_comum = FilaAtendimento()
fila_comum.entrar("Ana")
fila_comum.entrar("Bruno")

guiche_1 = Guiche("1", fila_comum)
guiche_2 = Guiche("2", fila_comum)

pessoa = guiche_1.atender_proximo()
print(formatar_atendimento(guiche_1.identificacao, pessoa))

pessoa = guiche_2.atender_proximo()
print(formatar_atendimento(guiche_2.identificacao, pessoa))

print("Total do guichê 1:", guiche_1.total_atendimentos)
print("Total do guichê 2:", guiche_2.total_atendimentos)

print("Tentativa com a fila vazia:")
total_antes = guiche_1.total_atendimentos
try:
    guiche_1.atender_proximo()
except ValueError as erro:
    print("Retirada rejeitada:", erro)
print("Total antes:", total_antes)
print("Total depois:", guiche_1.total_atendimentos)


print("\n=== FILAS EXCLUSIVAS ===")
fila_exclusiva_1 = FilaAtendimento()
fila_exclusiva_2 = FilaAtendimento()
fila_exclusiva_1.entrar("Rita")
fila_exclusiva_2.entrar("Rita")

guiche_3 = Guiche("3", fila_exclusiva_1)
guiche_4 = Guiche("4", fila_exclusiva_2)

pessoa = guiche_3.atender_proximo()
print(formatar_atendimento(guiche_3.identificacao, pessoa))

pessoa = guiche_4.atender_proximo()
print(formatar_atendimento(guiche_4.identificacao, pessoa))

print("Total do guichê 3:", guiche_3.total_atendimentos)
print("Total do guichê 4:", guiche_4.total_atendimentos)

Exemplo

O que deve aparecer no terminal

Na fila compartilhada, Ana é retirada pelo guichê 1 e Bruno pelo guichê 2. A terceira retirada é rejeitada, e o total do guichê 1 permanece igual a 1.

Nas filas exclusivas, cada fila contém sua própria ocorrência de Rita. Por isso, os guichês 3 e 4 conseguem atender uma pessoa, mesmo depois de um deles realizar a retirada.

Dica

Observe a ordem dentro de atender_proximo

A retirada ocorre antes do incremento. Se retirar_proxima() sinalizar que a fila está vazia, a execução não chega à atualização de total_atendimentos. Assim, a fila protege sua regra e o guichê não registra um atendimento que não aconteceu.

Registre e justifique suas observações

Revisão da implementação

Após executar o programa, responda:

  1. Qual trecho recebe e armazena a fila no guichê?
  2. Qual trecho realiza a delegação?
  3. Em que ordem as pessoas foram atendidas na fila compartilhada?
  4. O total mudou após a retirada rejeitada? Explique usando a ordem das instruções de atender_proximo.
  5. Por que Rita pôde ser atendida pelos dois guichês com filas exclusivas?
  6. Por que a formatação foi mantida como função?

Escreva pelo menos 180 caracteres (0/180).

Síntese do tutorial

Resumo

Critérios para compor objetos

Use estes critérios ao distribuir responsabilidades em novos programas.

  • Separe responsabilidades de acordo com os estados e as regras que cada parte precisa controlar.
  • Receba o objeto colaborador explicitamente em __init__ e armazene sua referência em um atributo de instância.
  • Delegue operações pela interface pública do colaborador, sem copiar nem manipular seus dados internos.
  • Compartilhe uma instância quando os consumidores realmente precisarem observar e alterar o mesmo estado; crie instâncias distintas quando os estados precisarem ser independentes.
  • Atualize o estado do objeto consumidor somente depois que a operação delegada for bem-sucedida.
  • Prefira uma função quando a operação puder receber todos os dados por parâmetros e não precisar manter estado próprio.

Tutorial concluído

Parabéns! Você concluiu: Compor objetos com responsabilidades distintas

Concluído! Agora você pode distribuir responsabilidades entre objetos colaboradores sem concentrar estados e regras em uma única classe.

60 XP

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso