Vou salvar uma carga útil de string no banco de dados. Eu tenho duas configurações globais:
- criptografia
- compressão
Eles podem ser ativados ou desativados usando a configuração de maneira que apenas um deles esteja ativado, ambos estejam ativados ou desativados.
Minha implementação atual é esta:
if (encryptionEnable && !compressEnable) {
encrypt(data);
} else if (!encryptionEnable && compressEnable) {
compress(data);
} else if (encryptionEnable && compressEnable) {
encrypt(compress(data));
} else {
data;
}
Estou pensando no padrão Decorator. É a escolha certa ou existe talvez uma alternativa melhor?
design
design-patterns
object-oriented-design
refactoring
Damith Ganegoda
fonte
fonte
if
declarações?Respostas:
Ao criar código, você sempre tem duas opções.
Não vou me concentrar no primeiro dos dois, porque realmente não há nada a ser dito. Se você quiser que ele funcione, deixe o código como está.
Mas o que aconteceria, se você escolhesse fazê-lo da maneira pedante e realmente resolvesse o problema com os padrões de design, da maneira que queria?
Você pode observar o seguinte processo:
Ao projetar o código OO, a maioria dos
if
s que estão em um código não precisa estar lá. Naturalmente, se você deseja comparar dois tipos escalares, comoint
s oufloat
s, é provável que tenha umif
, mas se desejar alterar os procedimentos com base na configuração, poderá usar o polimorfismo para obter o que deseja, mover as decisões (oif
s) da lógica de negócios para um local onde os objetos são instanciados - para as fábricas .A partir de agora, seu processo pode percorrer 4 caminhos separados:
data
não é criptografado nem compactado (não ligue, retornedata
)data
está compactado (liguecompress(data)
e retorne)data
está criptografado (ligueencrypt(data)
e devolva)data
está compactado e criptografado (chameencrypt(compress(data))
e devolva)Apenas olhando os 4 caminhos, você encontra um problema.
Você tem um processo que chama 3 (teoricamente 4, se você não contar nada como um) métodos diferentes que manipulam os dados e os retornam. Os métodos têm nomes diferentes , diferente da API pública (a maneira pela qual os métodos comunicam seu comportamento).
Usando o padrão do adaptador , podemos resolver a colisão de nomes (podemos unir a API pública) que ocorreu. Simplificando, o adaptador ajuda duas interfaces incompatíveis a trabalharem juntas. Além disso, o adaptador funciona definindo uma nova interface do adaptador, que as classes que tentam unir sua API implementam.
Suponho que agora você pode ter duas classes responsáveis pela compactação e criptografia.
No mundo corporativo, mesmo essas classes específicas provavelmente serão substituídas por interfaces, como a
class
palavra - chave seria substituída porinterface
(se você estiver lidando com linguagens como C #, Java e / ou PHP) ou aclass
palavra - chave permaneceria, mas oCompress
eEncrypt
métodos seriam definidos como um virtual puro , caso você codifique em C ++.Para fazer um adaptador, definimos uma interface comum.
Então temos que fornecer implementações da interface para torná-la útil.
Ao fazer isso, você acaba com 4 classes, cada uma fazendo algo completamente diferente, mas cada uma delas fornecendo a mesma API pública. O
Process
métodoNa sua lógica de negócios, onde você lida com a decisão none / encryption / compressão / both, você projetará seu objeto para fazê-lo depender da
DataProcessing
interface que projetamos anteriormente.O processo em si pode ser tão simples como este:
Não há mais condicionais. A turma
DataService
não tem idéia do que realmente será feito com os dados quando eles forem passados para odataProcessing
membro, e realmente não se importa com isso, não é de sua responsabilidade.Idealmente, você teria testes de unidade testando as 4 classes de adaptadores que você criou para garantir que funcionem e faça seu teste ser aprovado. E se eles forem aprovados, você pode ter certeza de que eles funcionarão, não importa onde você os chame em seu código.
Então, fazendo assim, nunca mais terei
if
s no meu código?Não. É menos provável que você tenha condicionais na lógica de negócios, mas eles ainda precisam estar em algum lugar. O lugar é suas fábricas.
E isso é bom. Você separa as preocupações da criação e realmente usa o código. Se você torna suas fábricas confiáveis (em Java, você pode até usar algo como a estrutura Guice do Google), em sua lógica de negócios, não está preocupado em escolher a classe certa a ser injetada. Porque você sabe que suas fábricas funcionam e entregará o que é pedido.
É necessário ter todas essas classes, interfaces etc.?
Isso nos leva de volta ao começo.
No OOP, se você escolher o caminho para usar o polimorfismo, realmente deseja usar padrões de design, deseja explorar os recursos da linguagem e / ou deseja seguir a ideologia de tudo é um objeto, então é. E mesmo assim, este exemplo não mesmo mostrar todas as fábricas que você vai precisar e se você tivesse que refazer os
Compression
eEncryption
aulas e torná-los interface em vez disso, você tem que incluir suas implementações bem.No final, você acaba com centenas de pequenas classes e interfaces, focadas em coisas muito específicas. O que não é necessariamente ruim, mas pode não ser a melhor solução para você, se tudo o que você deseja é fazer algo tão simples quanto adicionar dois números.
Se você deseja fazê-lo rapidamente, pode pegar a solução do Ixrec , que pelo menos conseguiu eliminar os blocos
else if
eelse
, que, na minha opinião, são até um pouco piores que os comunsif
.Atualização 2: Houve uma discussão selvagem sobre a primeira versão da minha solução. Discussão causada principalmente por mim, pela qual peço desculpas.
Decidi editar a resposta de uma maneira que é uma das maneiras de olhar para a solução, mas não a única. Também removi a parte do decorador, onde eu quis dizer fachada, que no final decidi deixar de fora completamente, porque um adaptador é uma variação da fachada.
fonte
Compression
eEncryption
parecem totalmente supérfluas. Não tenho certeza se você está sugerindo que eles são de alguma forma necessários para o processo de decoração ou apenas implica que eles representam conceitos extraídos. A segunda questão é que a criação de uma classeCompressionEncryptionDecorator
leva ao mesmo tipo de explosão combinatória que as condicionais do OP. Também não estou vendo o padrão do decorador com clareza suficiente no código sugerido.O único problema que vejo no seu código atual é o risco de explosão combinatória à medida que você adiciona mais configurações, que podem ser facilmente mitigadas estruturando o código da seguinte maneira:
Não conheço nenhum "padrão de design" ou "idioma" do qual possa ser considerado um exemplo.
fonte
else
entre minhas duas declarações if e por que estou atribuindodata
cada vez. Se os dois sinalizadores forem verdadeiros, o compress () será executado, e o encrypt () será executado no resultado do compress (), como você deseja.Eu acho que sua pergunta não está procurando praticidade, nesse caso a resposta do lxrec é a correta, mas para aprender sobre padrões de design.
Obviamente, o padrão de comando é um exagero para um problema tão trivial quanto o que você propõe, mas para fins de ilustração, aqui está:
Como você vê, colocar os comandos / transformação em uma lista permite que eles sejam executados sequencialmente. Obviamente, ele executará ambos, ou apenas um deles dependerá do que você colocar na lista sem as condições.
Obviamente, os condicionais terminarão em algum tipo de fábrica que reúne a lista de comandos.
EDITAR para o comentário de @ texacre:
Existem muitas maneiras de evitar as condições if na parte criadora da solução, vamos usar, por exemplo, um aplicativo GUI de desktop . Você pode ter caixas de seleção para as opções de compactar e criptografar. No
on clic
caso dessas caixas de seleção, instancie o comando correspondente e adicione-o à lista ou remova-o da lista se estiver desmarcando a opção.fonte
commands.add(new EncryptCommand());
oucommands.add(new CompressCommand());
respectivamente.Eu acho que os "padrões de design" são desnecessariamente direcionados para "oo padrões" e evitam completamente idéias muito mais simples. O que estamos falando aqui é um pipeline de dados (simples).
Eu tentaria fazê-lo em clojure. Qualquer outro idioma em que as funções sejam de primeira classe provavelmente também está ok. Talvez eu pudesse um exemplo de C # mais tarde, mas não é tão bom. Minha maneira de resolver isso seria os seguintes passos com algumas explicações para não-clojurianos:
1. Represente um conjunto de transformações.
Este é um mapa, ou seja, uma tabela de pesquisa / dicionário / o que for, de palavras-chave a funções. Outro exemplo (palavras-chave para strings):
Então, escrevendo
(transformations :encrypt)
ou(:encrypt transformations)
retornaria a função de criptografia. ((fn [data] ... )
é apenas uma função lambda.)2. Obtenha as opções como uma sequência de palavras-chave:
3. Filtre todas as transformações usando as opções fornecidas.
Exemplo:
4. Combine funções em uma:
Exemplo:
5. E então juntos:
O ONLY muda se queremos adicionar uma nova função, digamos "debug-print", é o seguinte:
fonte
funcs-to-run-here (map options funcs)
está fazendo a filtragem, escolhendo assim um conjunto de funções a serem aplicadas. Talvez eu deva atualizar a resposta e entrar em mais detalhes.[Essencialmente, minha resposta é uma continuação da resposta de @Ixrec acima . ]
Uma pergunta importante: o número de combinações distintas que você precisa cobrir vai crescer? Você conhece melhor o seu domínio de assunto. Este é o seu julgamento a fazer.
O número de variantes pode aumentar? Bem, isso não é inconcebível. Por exemplo, pode ser necessário acomodar mais algoritmos de criptografia diferentes.
Se você antecipar que o número de combinações distintas aumentará, o padrão de estratégia poderá ajudá-lo. Ele foi projetado para encapsular algoritmos e fornecer uma interface intercambiável para o código de chamada. Você ainda teria uma pequena quantidade de lógica ao criar (instanciar) a estratégia apropriada para cada sequência específica.
Você comentou acima que não espera que os requisitos sejam alterados. Se você não espera que o número de variantes aumente (ou se você pode adiar essa refatoração), mantenha a lógica do jeito que está. Atualmente, você tem uma quantidade pequena e gerenciável de lógica. (Talvez faça uma observação nos comentários sobre uma possível refatoração para um padrão de estratégia.)
fonte
Uma maneira de fazer isso no scala seria:
Usar o padrão decorador para atingir os objetivos acima (separação da lógica de processamento individual e como eles se conectam) seria muito detalhado.
Onde você exigiria um padrão de design para atingir esses objetivos em um paradigma de programação OO, a linguagem funcional oferece suporte nativo usando funções como cidadãos de primeira classe (linhas 1 e 2 no código) e composição funcional (linha 3)
fonte