Exercícios — Padrões de Projeto: Vocabulário, Imutabilidade e a Gênese Segura do Objeto

Aula 11 — Programação Orientada a Objetos

Autor

Marcos M. Raimundo — Instituto de Computação, UNICAMP

Aula Soluções

Questões discursivas

  1. (Vocabulário e Famílias de Padrões) Um colega de equipe propõe “criar uma classe que decide, com base num arquivo de configuração lido na inicialização, qual implementação de Cache (em memória ou em disco) o sistema usará, sem que nenhum outro módulo precise saber qual das duas está ativa”. Classifique essa proposta numa das três famílias de padrões do GoF, justificando pelo propósito arquitetural (não pelo nome de um padrão específico), e explique por que essa proposta não pode ser corretamente descrita como um framework.

  2. (Aliasing e Value Object) Explique detalhadamente a mecânica física que ocorre na memória (Stack e Heap) no fenômeno de aliasing, usando como exemplo duas referências apontando para o mesmo Retangulo mutável. Em seguida, demonstre por que transformar Retangulo num Value Object elimina o problema sem exigir nenhuma “cópia defensiva”, e explique o que muda na forma como o equals() desse objeto deve ser implementado.

  3. (Builder e Factory Method: quando usar cada um) Builder e Factory Method resolvem os dois “problemas de gênese” desta aula, mas não são intercambiáveis. Considerando o PedidoBuilder e a NotificacaoPedidoFactory discutidos em aula, explique com precisão qual problema cada padrão resolve — um objeto com muitos parâmetros opcionais que precisam de validação cruzada, ou a escolha de qual classe concreta instanciar entre alternativas conhecidas — e descreva um cenário de negócio em que os dois padrões seriam aplicados juntos, um dentro do outro.

Questões de Verdadeiro/Falso

Cada bloco de 4 itens trata do mesmo tema. A questão só é considerada correta se todos os 4 itens forem julgados corretamente (deixar em branco tem penalidade de 20% da nota da questão).

NotaTeste 1 — O Vocabulário e a Natureza dos Padrões de Projeto
  • □ Uma equipe que copia o código-fonte de um projeto anterior para reaproveitar uma solução de design, sem adaptar as classes ao novo domínio, está aplicando corretamente um Padrão de Projeto no sentido em que o GoF definiu o termo.
  • □ Se um engenheiro descrever uma solução dizendo apenas “implemente um Observer aqui” para um colega que também domina o vocabulário GoF, essa frase provavelmente transmite mais informação estrutural precisa do que uma explicação equivalente sem o nome do padrão.
  • □ Um sistema construído inteiramente com funções globais e variáveis compartilhadas, sem nenhuma classe ou encapsulamento, se beneficiaria da mesma forma ao aplicar a estrutura de um padrão GoF que um sistema já orientado a objetos se beneficiaria.
  • □ Um algoritmo de ordenação otimizado para grandes volumes de dados, por mais sofisticado e reutilizável que seja, não se qualifica como um Padrão de Projeto no sentido do catálogo GoF, ainda que resolva um problema recorrente.
NotaTeste 2 — As Três Famílias de Padrões
  • □ Um padrão cujo objetivo é permitir que um objeto adicione responsabilidades novas dinamicamente, sem alterar sua classe original nem depender de herança estática, se encaixa melhor na família Estrutural do que na família de Criação.
  • □ Um sistema de checkout que usa duas soluções ao mesmo tempo — uma para decidir qual meio de pagamento processar (Strategy) e outra para decidir qual classe concreta de repositório instanciar (Factory Method) — está misturando indevidamente duas famílias de padrões incompatíveis entre si.
  • □ Um padrão cujo foco declarado é “isolar a complexidade do operador new e gerenciar o ciclo de vida na Heap” pertence, por definição, à família de Criação, independentemente de também usar composição internamente.
  • □ Classificar o Strategy como um padrão Comportamental (e não Estrutural) depende do fato de ele envolver uma interface implementada por múltiplas classes, e não do fato de seu propósito ser distribuir a decisão de “como fazer” no tempo de execução.
NotaTeste 3 — Aliasing e a Fragilidade do Estado Mutável
  • □ Em Retangulo r2 = r1;, se Retangulo for uma classe (não um primitivo), a variável r2 recebe uma cópia independente dos valores de largura e altura de r1 no momento da atribuição.
  • □ Um sistema que compartilha uma mesma instância de uma classe Configuracao mutável entre dois módulos independentes está estruturalmente exposto ao mesmo padrão de bug de aliasing descrito para o Retangulo, mesmo que o domínio seja diferente.
  • □ Aplicar uma “Cópia Defensiva” antes de cada compartilhamento de uma referência mutável elimina o risco de aliasing sem nenhum custo adicional de memória ou processamento sobre o sistema.
  • □ Se um método de uma classe imutável precisasse, para calcular seu retorno, ler o estado de outro objeto imutável passado como parâmetro, o risco de aliasing bug sobre esse parâmetro seria estruturalmente idêntico ao risco existente ao ler um parâmetro mutável.
NotaTeste 4 — Entidade vs. Value Object
  • □ Um sistema bancário que precisa diferenciar duas contas correntes com saldo, titular e agência idênticos (por serem contas efetivamente distintas, uma de cada cliente) exige que ContaCorrente seja modelada como Entidade, não como Value Object.
  • □ Se dois objetos Dinheiro representarem quantias e moedas idênticas, mas tiverem sido instanciados em pontos completamente diferentes do código, equals() entre eles deve retornar false, pois cada instância ocupa um endereço de memória próprio na Heap.
  • □ Uma classe Endereco (rua, número, cidade, CEP) usada apenas para exibir a localização de entrega de um Pedido, sem que o sistema precise rastrear “este endereço específico” ao longo do tempo, é uma candidata natural a Value Object.
  • □ Modelar Aluno como Entidade (identidade por RA) não impede que um dos seus atributos, como Endereco, seja internamente representado por um Value Object imutável.
NotaTeste 5 — Obsessão por Primitivos e Anemia Semântica
  • □ Representar uma idade humana como um int sem nenhuma validação adicional permite, do ponto de vista do compilador Java, que o valor -500 seja atribuído sem qualquer erro de compilação.
  • □ Se duas partes distintas do sistema validarem o formato de um CPF de forma ligeiramente diferente (uma aceitando pontuação, outra não), isso é uma consequência esperada e inofensiva de representar CPF como String em vez de como um Value Object dedicado.
  • □ Substituir um parâmetro double taxaDeJuros por um Value Object TaxaDeJuros que valida, no construtor, que o valor está entre 0 e 1, move um erro de negócio que antes só apareceria em produção para o momento da criação do objeto.
  • □ A “Anemia Semântica” ocorre quando o tipo de um dado (ex.: String) descreve completamente o que esse dado representa para o negócio, sem nenhuma perda de informação em relação a um tipo de domínio dedicado.
NotaTeste 6 — O Padrão Builder e a Interface Fluente
  • □ No PedidoBuilder, o fato de cada método de configuração retornar this é o que torna sintaticamente possível encadear .paraCliente(...).comItem(...).comDesconto(...) numa única expressão.
  • □ Se a validação “desconto não pode ser maior que o total” fosse movida do método build() para dentro de comDesconto(), ela continuaria funcionando corretamente mesmo que comItem() fosse chamado depois de comDesconto() na cadeia de chamadas.
  • □ Um PedidoBuilder cujo construtor da classe Pedido alvo fosse public (em vez de private) ainda impediria, com a mesma eficácia, que código externo criasse um Pedido sem passar pelas validações do build().
  • □ A imutabilidade do objeto Pedido produzido no final não exige que o próprio PedidoBuilder, enquanto acumula dados, também seja imutável — os dois objetos podem ter regras de mutabilidade diferentes por servirem a propósitos diferentes.
NotaTeste 7 — Factory Method e Desacoplamento
  • □ Se NotificacaoPedidoFactory.criar(String meio) retornasse o tipo concreto NotificacaoPedidoEmail em vez da interface NotificacaoPedido quando o meio fosse “EMAIL”, o código cliente que só espera a interface ainda compilaria sem nenhuma alteração.
  • □ Adicionar uma nova implementação NotificacaoPedidoWhatsApp exige, necessariamente, alterar tanto a fábrica quanto todos os pontos do sistema que hoje chamam NotificacaoPedidoFactory.criar(...).
  • □ Um sistema de geração de convites de evento que decide, a partir de uma preferência do organizador, se instancia um ConviteDigital ou um ConviteImpresso, centralizando essa decisão numa única classe fábrica, replica a mesma lógica estrutural do Factory Method do e-commerce.
  • □ Se a fábrica de notificações precisar de credenciais de um gateway de SMS para construir NotificacaoPedidoSMS, essas credenciais podem ficar inteiramente confinadas à fábrica, sem que o código cliente que chama criar(...) precise conhecê-las.
NotaTeste 8 — Inversão de Dependência na Criação
  • □ Um ServicoDePedidos que instancia new RepositorioPedidosMySQL() diretamente dentro de seu próprio construtor pode ser substituído, em um teste automatizado, por uma versão que usa um repositório falso (mock) sem nenhuma alteração no código de ServicoDePedidos.
  • □ Injetar uma RepositorioPedidosFactory no construtor de ServicoDePedidos, em vez de instanciar diretamente uma classe concreta, empurra o conhecimento sobre “qual banco de dados usar” para a periferia do sistema, mantendo o núcleo de negócio isolado dessa decisão.
  • □ Um ServicoDePedidos que depende apenas da interface RepositorioPedidos ainda estaria violando o Princípio de Inversão de Dependência se essa interface fosse implementada por uma única classe concreta em todo o sistema.
  • □ A Inversão de Dependência na criação é estruturalmente análoga à injeção da interface Pagavel no Pedido feita na Aula 10 — em ambos os casos, um módulo de alto nível passa a depender de uma abstração em vez de uma implementação concreta.