Soluções — Síntese Arquitetural: Núcleo Estável, Periferia Volátil e o Encerramento do Curso

Aula 14 — Programação Orientada a Objetos (Aula de Encerramento)

Autor

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

Aula Exercícios

NotaTeste 1 — Núcleo Estável vs. Periferia Volátil
  • □ Um módulo que implementa uma regra de cálculo de comissão de vendas, alterada a cada campanha promocional trimestral pelo departamento de marketing, deveria ser tratado como periferia volátil, mesmo estando fisicamente dentro do mesmo pacote de código que as regras centrais de faturamento.
  • □ Se um sistema não tiver nenhuma interface explícita entre suas camadas, isso automaticamente implica que ele não possui nenhum núcleo estável, mesmo que parte de seu código de fato mude com muito menos frequência que o restante.
  • □ Um framework de testes automatizados que nunca precisa ser trocado ao longo de toda a vida de um projeto, mas que não expressa nenhuma regra de negócio do domínio, não deveria ser classificado como núcleo estável apenas por sua baixa frequência de mudança — a definição de núcleo usada nesta aula exige também conter contratos ou regras de negócio do domínio.
  • □ Um sistema acadêmico de matrícula em que a lógica de “o que conta como pré-requisito satisfeito” muda a cada reforma curricular, enquanto o mecanismo de armazenamento em banco de dados permanece intacto desde o lançamento do sistema, deveria tratar essa lógica de pré-requisitos como periferia volátil apesar de ela expressar uma regra do domínio acadêmico — porque o que determina a classificação é a frequência real de mudança, não a categoria intuitiva “regra de negócio = núcleo”.
Dica(Resposta) Teste 1 — Núcleo Estável vs. Periferia Volátil
  • ✔ Verdadeiro — A classificação núcleo/periferia é uma questão de frequência e origem da mudança, não de localização física no código-fonte. Uma regra de comissão alterada trimestralmente por pressão de marketing tem exatamente o perfil de mudança da periferia volátil, ainda que resida no mesmo pacote (ou até na mesma classe) que uma regra de faturamento estável — os dois merecem tratamento arquitetural diferente independentemente de onde estão fisicamente.
  • ✗ Falso — A existência de um núcleo estável é uma propriedade de frequência real de mudança de um conjunto de regras, não uma propriedade que depende de haver interfaces formais já desenhadas. Um sistema mal arquitetado (sem nenhuma interface) ainda pode ter partes que mudam raramente — elas simplesmente não estão isoladas da periferia por uma abstração, o que é justamente o problema a corrigir, não uma prova de que “núcleo estável” não existe ali.
  • ✔ Verdadeiro — A aula define o núcleo estável por dois critérios conjuntos: baixa frequência de mudança e conter contratos/regras de negócio abstratas do domínio. Um framework de testes satisfaz apenas o primeiro critério — pode ser extremamente estável sem, no entanto, expressar nenhuma regra de negócio. “Estável” é condição necessária, não suficiente, para ser núcleo; confundir os dois é o erro que este item testa.
  • ✔ Verdadeiro — A intuição comum equipara “regra de negócio” a “núcleo estável” e “banco de dados” a “periferia volátil” só pela categoria técnica vs. de domínio. Este caso-limite mostra que essa equiparação automática pode falhar: se a lógica de pré-requisitos muda com alta frequência (a cada reforma curricular), ela se comporta como periferia volátil mesmo sendo, em espécie, uma regra de domínio — a classificação correta depende da frequência real observada, não do rótulo “técnico” ou “de negócio”.
NotaTeste 2 — A Regra de Ouro da Dependência
  • □ Se uma classe do núcleo estável instanciar diretamente (via new) uma classe concreta pertencente à periferia volátil, isso por si só já viola a Regra de Ouro, independentemente de existir ou não uma interface abstrata entre as duas.
  • □ Em um sistema onde tanto o núcleo quanto a periferia dependem exclusivamente de interfaces definidas dentro do próprio núcleo, a Regra de Ouro estaria sendo respeitada mesmo que a periferia contenha a maior parte das linhas de código do sistema.
  • □ Se duas classes voláteis da periferia dependerem diretamente uma da outra (sem passar por nenhuma abstração do núcleo), isso por si só já é uma violação da Regra de Ouro, do mesmo jeito que seria se o núcleo dependesse diretamente da periferia.
  • □ Um sistema em que o núcleo depende de uma interface, e essa interface é implementada por uma única classe concreta que nunca muda ao longo de toda a vida do projeto, ainda se beneficia estruturalmente da Regra de Ouro, mesmo sem nunca ter havido uma segunda implementação até hoje.
Dica(Resposta) Teste 2 — A Regra de Ouro da Dependência
  • ✔ Verdadeiro — A Regra de Ouro fala da direção da dependência de compilação/instanciação, não apenas de “existir uma interface em algum lugar do sistema”. Se o núcleo usa new diretamente sobre um tipo concreto volátil, o núcleo passa a depender fisicamente dessa classe (precisa recompilar se ela mudar de pacote, por exemplo) — mesmo que uma interface exista e não seja usada nesse ponto específico, a violação já ocorreu ali.
  • ✔ Verdadeiro — A Regra de Ouro é sobre a direção da dependência (sempre em direção à abstração, nunca do núcleo diretamente para uma implementação concreta), não sobre a proporção de código em cada lado. Um sistema pode ter uma periferia enorme em volume de código e ainda respeitar plenamente a regra, desde que todas as dependências apontem corretamente para as interfaces do núcleo.
  • ✗ Falso — A Regra de Ouro protege especificamente o núcleo estável contra a volatilidade da periferia — ela não proíbe que duas peças de infraestrutura conversem diretamente entre si (por exemplo, um driver de banco de dados chamando uma biblioteca de rede). O risco arquitetural que a regra endereça é o núcleo depender do que muda, não qualquer dependência concreta que exista nos bastidores do sistema.
  • ✔ Verdadeiro — O benefício estrutural da Regra de Ouro (a mudança na periferia não se propaga ao núcleo) não depende de a troca efetivamente já ter acontecido — depende apenas de a dependência apontar corretamente para a abstração. Mesmo com uma única implementação histórica, o sistema já está protegido caso essa implementação precise mudar no futuro; a ausência de troca até agora não invalida a proteção estrutural já existente.
NotaTeste 3 — Strategy vs. Adapter na Matriz de Decisão
  • □ Um sistema que já usa Strategy para escolher entre três algoritmos de cálculo de frete, todos implementando a mesma interface CalculadoraDeFrete definida internamente, não precisaria do Adapter mesmo que decidisse futuramente integrar um serviço de frete de terceiros cuja API pública tenha uma assinatura de método completamente diferente da interface CalculadoraDeFrete.
  • □ Se um SDK de terceiros já for compatível, desde o primeiro dia, com a interface que o núcleo do sistema espera, ainda seria estruturalmente válido envolvê-lo num Adapter, embora esse Adapter faria apenas repasse puro, sem nenhuma tradução real de assinatura.
  • □ Um sistema de recomendação que troca, em tempo de execução, entre diferentes algoritmos de ranqueamento de produtos — todos já implementando a mesma interface RankeadorDeProdutos do próprio sistema — está resolvendo um problema de acoplamento condicional, não sintático, e por isso a ferramenta correta da matriz é o Strategy, não o Adapter.
  • □ Como tanto Strategy quanto Adapter usam injeção de dependência via construtor, qualquer classe que receba uma dependência dessa forma está necessariamente aplicando um dos dois padrões, mesmo que a dependência recebida não varie nem precise de tradução de interface.
Dica(Resposta) Teste 3 — Strategy vs. Adapter na Matriz de Decisão
  • ✗ Falso — Strategy resolve a variação entre algoritmos que já falam a mesma língua (a mesma interface); ele não resolve incompatibilidade de assinatura. Se o novo serviço de terceiros tem uma API com assinatura diferente da interface CalculadoraDeFrete, é necessário um Adapter para traduzir essa assinatura antes que o serviço possa, então, ser usado como mais uma implementação de CalculadoraDeFrete dentro do Strategy já existente — os dois padrões se combinam, não se substituem.
  • ✔ Verdadeiro — Um Adapter cuja tradução é trivial (repasse puro, sem nenhuma conversão real) ainda é estruturalmente válido — é o caso degenerado do padrão, análogo ao Decorator com zero camadas ainda sendo uma aplicação válida da recursão. A validade estrutural do padrão não depende de a tradução ser complexa, só de a topologia (interface esperada, classe adaptada, adaptador entre as duas) estar presente.
  • ✔ Verdadeiro — Como todos os algoritmos já compartilham a mesma interface própria do sistema, não há nenhuma incompatibilidade de assinatura a resolver — o problema é puramente de qual algoritmo escolher em cada contexto, a definição exata do acoplamento condicional que o Strategy ataca.
  • ✗ Falso — Injeção de dependência via construtor é uma técnica geral, usada por praticamente todo o repertório de padrões deste curso (State, Decorator, Observer também a usam) e até fora de qualquer padrão nomeado. Nem toda classe que recebe uma dependência pelo construtor está aplicando Strategy ou Adapter — é preciso que exista, especificamente, variação de algoritmo (Strategy) ou incompatibilidade de interface a traduzir (Adapter) para que o nome do padrão se aplique.
NotaTeste 4 — Decorator vs. Observer na Matriz de Decisão
  • □ Um sistema de assinatura de streaming que permite empilhar, de forma independente e cumulativa, complementos como “sem anúncios”, “qualidade 4K” e “download offline” sobre um plano-base, está diante de um cenário mais bem resolvido pela intenção principal do Decorator do que pela do Observer.
  • □ Se um sistema de pedidos precisar apenas notificar exatamente um sistema de log fixo, que nunca mudará ao longo de toda a vida da aplicação, aplicar o Observer nesse caso não traria nenhuma vantagem estrutural sobre uma chamada direta e permanente a esse único sistema de log.
  • □ Um cenário em que um pedido precisa acumular selos de fidelidade opcionais (Decorator) e, ao mesmo tempo, notificar um número variável de sistemas de estoque (Observer) exige escolher apenas um dos dois padrões, já que ambos operam sobre decisões tomadas em tempo de execução.
  • □ Um sistema de edição de texto colaborativo em que múltiplos cursores de usuários diferentes precisam ser avisados sempre que o documento compartilhado é alterado, sem que o documento conheça quantos cursores existem, está aplicando a mesma estrutura de acoplamento temporal/identidade que o Observer resolve para o Pedido.
Dica(Resposta) Teste 4 — Decorator vs. Observer na Matriz de Decisão
  • ✔ Verdadeiro — Responsabilidades opcionais, cumulativas e combináveis livremente sobre um único objeto são exatamente a assinatura do Decorator (a mesma estrutura da cafeteria e da cadeia de notificadores) — não há aqui nenhum cenário de “um evento dispara reações em múltiplos subsistemas”, que é o que o Observer resolve.
  • ✗ Falso — Mesmo com um único assinante fixo, o Observer ainda garante que o núcleo não conheça o tipo concreto do observador — o Pedido continua dependendo só de uma interface abstrata, não da classe LogService especificamente. Esse desacoplamento de identidade tem valor estrutural (testabilidade, possibilidade de substituição por um mock, por exemplo) independentemente de o número de assinantes nunca mudar na prática.
  • ✗ Falso — Operar em tempo de execução é uma característica que os dois padrões compartilham, mas o tipo de problema que cada um resolve é ortogonal (acumular responsabilidades sobre um único objeto vs. notificar múltiplos objetos independentes) — exatamente como Decorator para a cadeia de notificadores e Observer para o acoplamento de notificação em cascata coexistiram sobre o mesmo Pedido, sem conflito.
  • ✔ Verdadeiro — O documento (Sujeito) notificando um número variável de cursores (Observadores) sem conhecê-los por nome é exatamente a topologia Subject/Observer — mesma estrutura, domínio diferente.
NotaTeste 5 — Aplicando a Matriz a um Cenário Novo
  • □ Um sistema de emissão de notas fiscais que precisa se adaptar a três formatos de arquivo XML distintos exigidos por diferentes prefeituras, cada formato com uma estrutura de tags incompatível entre si, mas mantendo uma única interface interna GeradorDeNotaFiscal, está diante de um cenário cuja intenção principal bate com a do Adapter, não com a do Strategy.
  • □ Um sistema de jogos que precisa trocar dinamicamente o comportamento de movimento de um personagem (andar, voar, nadar) conforme o terreno em que ele se encontra, todos os comportamentos já implementando a mesma interface ComportamentoDeMovimento definida pelo próprio jogo, está diante de um cenário resolvido pela intenção do Adapter, já que o comportamento muda em tempo de execução.
  • □ Um sistema de pagamento que precisa acumular, de forma opcional e cumulativa, taxas de conveniência, seguro de compra e parcelamento sobre o valor de uma transação, sem que o número de combinações possíveis gere uma nova subclasse para cada combinação, está diante de um cenário cuja intenção principal bate com a do Decorator.
  • □ Um sistema de alerta de estoque que precisa avisar automaticamente um número variável de compradores cadastrados sempre que um produto esgotado volta a ficar disponível, sem que o estoque conheça a identidade concreta de cada comprador cadastrado, está diante de um cenário cuja intenção principal bate com a do Observer.
Dica(Resposta) Teste 5 — Aplicando a Matriz a um Cenário Novo
  • ✔ Verdadeiro — O problema central é a incompatibilidade sintática entre formatos externos (exigidos por terceiros, as prefeituras) e o contrato interno do sistema — exatamente a coluna “Quando utilizar” do Adapter na matriz, não a de Strategy (que pressupõe algoritmos já compatíveis entre si).
  • ✗ Falso — “Mudar em tempo de execução” não é, por si só, sinônimo de Adapter — é uma característica que Strategy também tem. Como todos os comportamentos já implementam a mesma interface própria do jogo (sem nenhuma incompatibilidade de assinatura a traduzir), o problema é de variação de algoritmo por contexto, a intenção principal do Strategy, não do Adapter.
  • ✔ Verdadeiro — Responsabilidades (aqui, encargos financeiros) opcionais e cumulativas sobre um único objeto, evitando a explosão combinatória de subclasses, é exatamente a definição de intenção do Decorator na matriz.
  • ✔ Verdadeiro — Um vínculo um-para-muitos, em que a mudança de estado (produto disponível novamente) aciona dependentes de forma anônima, é a definição de intenção principal do Observer na matriz.
NotaTeste 6 — Síntese do Curso: TRUE e o Custo de Mudança
  • □ Um sistema em que trocar o algoritmo de cálculo de frete exige apenas registrar uma nova implementação de CalculadoraDeFrete, sem alterar nenhuma classe já existente, satisfaz a propriedade Reasonable do TRUE de forma mais direta do que a propriedade Transparent, já que o custo da mudança (uma classe nova) é proporcional ao benefício (uma nova opção de frete).
  • □ Um sistema que aplica o Adapter para integrar um SDK de pagamento legado, mas cujo Adapter concreto não pode ser reaproveitado em nenhum outro contexto do sistema além daquele SDK específico, ainda estaria satisfazendo plenamente a propriedade Usable do TRUE, já que o Adapter isola a incompatibilidade sintática do núcleo.
  • □ Se um sistema apresentar o sintoma de fragilidade descrito na Aula 1 (quebrar em pontos sem conexão lógica com o que foi alterado) mesmo depois de aplicar Strategy, Adapter, Decorator e Observer em todos os pontos de variação conhecidos, isso indica necessariamente que os quatro padrões foram implementados incorretamente, e não que pode haver uma quinta fonte de acoplamento ainda não identificada.
  • □ A conclusão de que “os quatro padrões estudados neste curso são, em conjunto, uma resposta madura ao problema do custo de mudança” depende logicamente de cada um deles, isoladamente, já resolver sozinho todos os três sintomas (rigidez, fragilidade, imobilidade) apresentados na Aula 1.
Dica(Resposta) Teste 6 — Síntese do Curso: TRUE e o Custo de Mudança
  • ✔ Verdadeiro — Reasonable é definida, na Aula 1, exatamente como “o custo de qualquer mudança deve ser proporcional ao benefício que ela traz” — o cenário descrito (uma classe nova, um benefício novo, nenhuma reescrita) é o exemplo direto dessa propriedade. Transparent fala de consequências óbvias, não de proporcionalidade de custo, então o mapeamento com Reasonable é o mais direto entre os dois.
  • ✗ Falso — Isolar a incompatibilidade sintática do núcleo é, de fato, um ganho — mas é um ganho de Transparent (consequências contidas) e de proteção do núcleo, não de Usable, que é sobre o próprio código poder ser reaproveitado em contextos novos e inesperados. Um Adapter amarrado a um único SDK específico, sem nenhum potencial de reaproveitamento, não satisfaz Usable só por cumprir bem seu papel de isolamento — as duas propriedades do TRUE não são a mesma coisa.
  • ✗ Falso — A matriz desta aula cobre quatro tipos específicos de acoplamento (condicional, sintático, taxonômico, temporal/identidade) — mas não afirma que são os únicos tipos de acoplamento nocivo possíveis em qualquer sistema. Persistência de fragilidade depois de aplicar os quatro padrões corretamente é evidência mais forte de uma quinta fonte de acoplamento ainda não mapeada do que de erro de implementação nos quatro já aplicados.
  • ✗ Falso — A conclusão da aula é justamente o oposto: cada padrão ataca um sintoma/tipo de acoplamento específico e parcial; é a combinação complementar dos quatro — não a suficiência isolada de qualquer um deles — que responde ao problema mais amplo do custo de mudança. Exigir que um único padrão resolva sozinho todos os três sintomas contradiz a própria lógica da matriz apresentada nesta aula.