Eu olhei para algumas respostas e procurei no Google, mas não encontrei nada útil (isto é, que não teria efeitos colaterais estranhos).
Meu problema, em resumo, é que tenho um objeto e preciso executar uma longa sequência de operações nele; Eu penso nisso como uma espécie de linha de montagem, como construir um carro.
Eu acredito que esses objetos seriam chamados de objetos de método .
Portanto, neste exemplo, em algum momento, eu teria um CarWithoutUpholstery no qual precisaria executar installBackSeat, installFrontSeat, installWoodenInserts (as operações não interferem umas com as outras e podem até ser feitas em paralelo). Essas operações são executadas por CarWithoutUpholstery.worker () e produzem um novo objeto que seria um CarWithUpholstery, no qual eu executaria talvez cleanInsides (), confirmNoUpholsteryDefects () e assim por diante.
As operações em uma única fase já são independentes, ou seja, eu já estou lidando com um subconjunto delas que pode ser executado em qualquer ordem (os bancos dianteiro e traseiro podem ser instalados em qualquer ordem).
Minha lógica atualmente usa o Reflection para simplificar a implementação.
Ou seja, uma vez que eu tenho um CarWithoutUpholstery, o objeto se inspeciona para métodos chamados performSomething (). Nesse ponto, ele executa todos esses métodos:
myObject.perform001SomeOperation();
myObject.perform002SomeOtherOperation();
...
enquanto verifica erros e outras coisas. Embora a ordem de operação não seja importante, atribuí uma ordem lexicográfica, caso eu descubra que alguma ordem é importante, afinal. Isso contradiz o YAGNI , mas custou muito pouco - um tipo simples () - e poderia salvar um método massivo renomeando (ou introduzindo algum outro método para executar testes, por exemplo, uma variedade de métodos) mais adiante.
Um exemplo diferente
Digamos que, em vez de construir um carro, eu tenha que compilar um relatório da Polícia Secreta sobre alguém e enviá-lo ao meu Overlord do Mal . Meu objeto final será um ReadyReport. Para construí-lo, começo reunindo informações básicas (nome, sobrenome, cônjuge ...). Esta é minha Fase A. Dependendo se existe ou não um cônjuge, talvez eu precise seguir para as fases B1 ou B2 e reunir dados de sexualidade de uma ou duas pessoas. Isso é feito de várias consultas diferentes para diferentes Minions do Mal controlando a vida noturna, câmeras de rua, recibos de vendas de sex shops e outras coisas. E assim por diante.
Se a vítima não tiver família, nem entrarei na fase GetInformationAboutFamily, mas, se tiver, será irrelevante se eu visar primeiro o pai, a mãe ou os irmãos (se houver). Mas não posso fazer isso se não tiver realizado um FamilyStatusCheck, que, portanto, pertence a uma fase anterior.
Tudo funciona maravilhosamente ...
- se precisar de alguma operação adicional, preciso apenas adicionar um método privado,
- se a operação é comum a várias fases, posso herdá-la de uma superclasse,
- operações são simples e independentes. Nenhum valor de uma operação é sempre exigido por qualquer um dos outros (operações que não são realizadas numa fase diferente),
- os objetos abaixo da linha não precisam executar muitos testes, pois nem sequer poderiam existir se seus objetos criadores não tivessem verificado essas condições em primeiro lugar. Ou seja, ao colocar inserções no painel, limpar o painel e verificar o painel, não preciso verificar se um painel está realmente lá .
- permite testes fáceis. Posso facilmente zombar de um objeto parcial e executar qualquer método nele, e todas as operações são caixas-pretas determinísticas.
...mas...
O problema surgiu quando adicionei uma última operação em um dos meus objetos de método, o que fez com que o módulo geral excedesse um índice de complexidade obrigatório ("menos que N métodos privados").
Eu já levei o assunto para cima e sugeri que, neste caso, a riqueza de métodos privados não é um indicador de desastre. A complexidade é lá, mas está lá porque a operação é complexa, e realmente não é tudo que o complexo - é apenas longa .
Usando o exemplo do Overlord Maligno, meu problema é que o Overlord Maligno (também conhecido como Quem Não Deve Ser Negado ) solicitou todas as informações sobre a dieta, meus Minions Dietéticos me dizendo que eu preciso consultar restaurantes, cozinhas americanas, vendedores ambulantes, vendedores ambulantes sem licença, estufa proprietários etc., e o (sub) Overlord do Mal - conhecido como Aquele Que Também Não Deve Ser Negado - reclamando que estou realizando muitas consultas na fase GetDietaryInformation.
Nota : Estou ciente de que, sob vários pontos de vista, isso não é um problema (ignorando possíveis problemas de desempenho etc.). Tudo o que está acontecendo é que uma métrica específica é infeliz e há justificativa para isso.
O que eu acho que poderia fazer
Além da primeira, todas essas opções são factíveis e, acho, defensáveis.
- Eu verifiquei que posso ser sorrateiro e declarar metade dos meus métodos
protected
. Mas eu estaria explorando uma fraqueza no procedimento de teste e, além de me justificar quando pego, não gosto disso. Além disso, é uma medida paliativa. E se o número de operações necessárias dobrar? Improvável, mas e então? - Eu posso dividir arbitrariamente essa fase em AnnealedObjectAlpha, AnnealedObjectBravo e AnnealedObjectCharlie, e ter um terço das operações sendo executadas em cada estágio. Tenho a impressão de que isso realmente adiciona complexidade (mais N-1 classes), sem nenhum benefício, exceto para passar no teste. É claro que posso sustentar que CarWithFrontSeatsInstalled e CarWithAllSeatsInstalled são estágios logicamente sucessivos . O risco de um método Bravo ser posteriormente requerido pelo Alpha é pequeno e ainda menor se eu o jogar bem. Mas ainda.
- Posso agrupar operações diferentes, remotamente semelhantes, em uma única.
performAllSeatsInstallation()
. Essa é apenas uma medida paliativa e aumenta a complexidade da operação única. Se eu precisar executar as operações A e B em uma ordem diferente, e eu as empacotar dentro de E = (A + C) e F (B + D), precisarei desmembrar E e F e embaralhar o código . - Posso usar uma matriz de funções lambda e evitar a verificação completamente, mas acho isso desajeitado. Esta é, no entanto, a melhor alternativa até agora. Livraria-se da reflexão. Os dois problemas que tenho é que provavelmente seria solicitado a reescrever todos os objetos de método, não apenas os hipotéticos
CarWithEngineInstalled
, e embora isso fosse uma segurança de trabalho muito boa, ele realmente não é tão atraente; e que o verificador de cobertura de código tem problemas com lambdas (que são solucionáveis, mas ainda assim ).
Então...
- Qual, você acha, é a minha melhor opção?
- Existe uma maneira melhor de não considerar? ( talvez seja melhor eu ficar limpo e perguntar diretamente o que é isso? )
- Esse design é irremediavelmente falho, e é melhor eu admitir derrota e abandonar - essa arquitetura completamente? Não é bom para minha carreira, mas escrever código mal projetado seria melhor a longo prazo?
- Minha escolha atual é realmente a única maneira verdadeira e preciso lutar para instalar métricas (e / ou instrumentação) de melhor qualidade? Para esta última opção, eu precisaria de referências ... Não posso apenas acenar com a mão no @PHB enquanto murmuro Essas não são as métricas que você está procurando . Não importa o quanto eu gostaria de poder