Ao criar código, você sempre tem duas opções.
- basta fazê-lo, caso em que praticamente qualquer solução funcionará para você
- ser pedante e projetar uma solução que explore as peculiaridades da linguagem e sua ideologia (linguagens OO, neste caso - o uso do polimorfismo como um meio para fornecer a decisão)
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 ifs que estão em um código não precisa estar lá. Naturalmente, se você deseja comparar dois tipos escalares, como ints ou floats, é provável que tenha um if, mas se desejar alterar os procedimentos com base na configuração, poderá usar o polimorfismo para obter o que deseja, mover as decisões (o ifs) 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:
datanão é criptografado nem compactado (não ligue, retorne data)
dataestá compactado (ligue compress(data)e retorne)
dataestá criptografado (ligue encrypt(data)e devolva)
dataestá compactado e criptografado (chame encrypt(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.
Esta não é uma linguagem concreta. É uma abordagem genérica, a palavra-chave any está lá para representar que pode ser de qualquer tipo, em uma linguagem como C #, você pode substituí-la por genéricos ( <T>).
Suponho que agora você pode ter duas classes responsáveis pela compactação e criptografia.
class Compression
{
Compress(data : any) : any { ... }
}
class Encryption
{
Encrypt(data : any) : any { ... }
}
No mundo corporativo, mesmo essas classes específicas provavelmente serão substituídas por interfaces, como a classpalavra - chave seria substituída por interface(se você estiver lidando com linguagens como C #, Java e / ou PHP) ou a classpalavra - chave permaneceria, mas o Compresse Encryptmétodos seriam definidos como um virtual puro , caso você codifique em C ++.
Para fazer um adaptador, definimos uma interface comum.
interface DataProcessing
{
Process(data : any) : any;
}
Então temos que fornecer implementações da interface para torná-la útil.
// when neither encryption nor compression is enabled
class DoNothingAdapter : DataProcessing
{
public Process(data : any) : any
{
return data;
}
}
// when only compression is enabled
class CompressionAdapter : DataProcessing
{
private compression : Compression;
public Process(data : any) : any
{
return this.compression.Compress(data);
}
}
// when only encryption is enabled
class EncryptionAdapter : DataProcessing
{
private encryption : Encryption;
public Process(data : any) : any
{
return this.encryption.Encrypt(data);
}
}
// when both, compression and encryption are enabled
class CompressionEncryptionAdapter : DataProcessing
{
private compression : Compression;
private encryption : Encryption;
public Process(data : any) : any
{
return this.encryption.Encrypt(
this.compression.Compress(data)
);
}
}
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 Processmétodo
Na 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 DataProcessinginterface que projetamos anteriormente.
class DataService
{
private dataProcessing : DataProcessing;
public DataService(dataProcessing : DataProcessing)
{
this.dataProcessing = dataProcessing;
}
}
O processo em si pode ser tão simples como este:
public ComplicatedProcess(data : any) : any
{
data = this.dataProcessing.Process(data);
// ... perhaps work with the data
return data;
}
Não há mais condicionais. A turma DataServicenão tem idéia do que realmente será feito com os dados quando eles forem passados para o dataProcessingmembro, 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 ifs 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 Compressione Encryptionaulas 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 ife else, que, na minha opinião, são até um pouco piores que os comuns if.
Leve em consideração que esta é a minha maneira de criar um bom design de OO. Codificando para interfaces em vez de implementações, é assim que eu faço nos últimos anos e é com essa abordagem que me sinto mais confortável.
Eu pessoalmente gosto mais da programação if-less e gostaria muito mais da solução mais longa nas 5 linhas de código. É o modo como estou acostumado a projetar código e me sinto muito confortável em lê-lo.
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.
ifdeclarações?