Eu tenho uma classe usada para processar pagamentos de clientes. Todos os métodos desta classe, exceto um, são os mesmos para todos os clientes, exceto um que calcula (por exemplo) quanto o usuário do cliente deve. Isso pode variar muito de cliente para cliente e não há uma maneira fácil de capturar a lógica dos cálculos em algo como um arquivo de propriedades, pois pode haver vários fatores personalizados.
Eu poderia escrever um código feio que alterna com base no customerID:
switch(customerID) {
case 101:
.. do calculations for customer 101
case 102:
.. do calculations for customer 102
case 103:
.. do calculations for customer 103
etc
}
mas isso requer a reconstrução da classe toda vez que obtemos um novo cliente. Qual é a melhor maneira?
[Editar] O artigo "duplicado" é completamente diferente. Não estou perguntando como evitar uma declaração de troca, estou pedindo o design moderno que melhor se aplica a este caso - que eu poderia resolver com uma declaração de troca se quisesse escrever código de dinossauro. Os exemplos fornecidos são genéricos e não são úteis, pois dizem essencialmente "Ei, a opção funciona muito bem em alguns casos, não em outros".
[Editar] Decidi ir com a resposta mais bem classificada (criar uma classe "Cliente" separada para cada cliente que implemente uma interface padrão) pelos seguintes motivos:
Consistência: posso criar uma interface que garanta que todas as classes de clientes recebam e retornem a mesma saída, mesmo se criadas por outro desenvolvedor
Manutenção: Todo o código é escrito na mesma linguagem (Java), portanto não é necessário que ninguém mais aprenda uma linguagem de codificação separada para manter o que deveria ser um recurso simples.
Reutilização: caso um problema semelhante apareça no código, eu posso reutilizar a classe Customer para armazenar qualquer número de métodos para implementar a lógica "personalizada".
Familiaridade: eu já sei como fazer isso, para que eu possa fazer isso rapidamente e passar para outras questões mais prementes.
Desvantagens:
Cada novo cliente requer uma compilação da nova classe Customer, o que pode adicionar alguma complexidade à maneira como compilamos e implantamos as alterações.
Cada novo cliente deve ser adicionado por um desenvolvedor - uma pessoa de suporte não pode simplesmente adicionar a lógica a algo como um arquivo de propriedades. Isso não é o ideal ... mas também não tinha certeza de como uma pessoa de Suporte seria capaz de escrever a lógica comercial necessária, especialmente se ela for complexa com muitas exceções (como é provável).
Não será bem dimensionado se adicionarmos muitos, muitos novos clientes. Isso não é esperado, mas se isso acontecer, teremos que repensar muitas outras partes do código, bem como esta.
Para os interessados, você pode usar o Java Reflection para chamar uma classe pelo nome:
Payment payment = getPaymentFromSomewhere();
try {
String nameOfCustomClass = propertiesFile.get("customClassName");
Class<?> cpp = Class.forName(nameOfCustomClass);
CustomPaymentProcess pp = (CustomPaymentProcess) cpp.newInstance();
payment = pp.processPayment(payment);
} catch (Exception e) {
//handle the various exceptions
}
doSomethingElseWithThePayment(payment);
fonte
Respostas:
Duas opções me vêm à mente.
Opção 1: torne sua classe uma classe abstrata, onde o método que varia entre os clientes é um método abstrato. Em seguida, crie uma subclasse para cada cliente.
Opção 2: Crie uma
Customer
classe ou umaICustomer
interface contendo toda a lógica dependente do cliente. Em vez de sua classe de processamento de pagamentos aceitar um ID de cliente, aceite umCustomer
ouICustomer
objeto. Sempre que precisa fazer algo dependente do cliente, chama o método apropriado.fonte
Você pode querer escrever os cálculos personalizados como "plug-ins" para seu aplicativo. Em seguida, você usaria um arquivo de configuração para informar a você qual plugin de cálculo deve ser usado para qual cliente. Dessa forma, seu aplicativo principal não precisaria ser recompilado para cada novo cliente - ele só precisa ler (ou reler) um arquivo de configuração e carregar novos plug-ins.
fonte
Eu usaria uma regra definida para descrever os cálculos. Isso pode ser mantido em qualquer armazenamento de persistência e modificado dinamicamente.
Como alternativa, considere isso:
Onde foi
customerOpsIndex
calculado o índice de operação correto (você sabe qual cliente precisa de qual tratamento).fonte
Algo como o abaixo:
observe que você ainda tem a instrução switch no repositório. No entanto, não há como contornar isso, em algum momento você precisa mapear um ID de cliente para a lógica necessária. Você pode ser inteligente e movê-lo para um arquivo de configuração, colocar o mapeamento em um dicionário ou carregar dinamicamente nos conjuntos lógicos, mas basicamente se resume a alternar.
fonte
Parece que você tem um mapeamento de 1 para 1 dos clientes para o código personalizado e, como você está usando uma linguagem compilada, precisa reconstruir o sistema sempre que recebe um novo cliente
Tente uma linguagem de script incorporada.
Por exemplo, se seu sistema estiver em Java, você poderá incorporar o JRuby e, em seguida, para cada cliente, armazenar um snippet correspondente do código Ruby. Idealmente, em algum lugar sob controle de versão, no mesmo ou em um repositório Git separado. E avalie esse trecho no contexto do seu aplicativo Java. O JRuby pode chamar qualquer função Java e acessar qualquer objeto Java.
Este é um padrão muito comum. Por exemplo, muitos jogos de computador são escritos em C ++, mas usam scripts Lua incorporados para definir o comportamento do cliente de cada oponente no jogo.
Por outro lado, se você tiver um mapeamento de 1 para 1 dos clientes para o código personalizado, use o padrão "Estratégia", como já sugerido.
Se o mapeamento não se basear na identificação do usuário, adicione uma
match
função a cada objeto de estratégia e faça uma escolha ordenada de qual estratégia usar.Aqui está algum pseudo-código
fonte
Eu vou nadar contra a corrente.
Eu tentaria implementar minha própria linguagem de expressão com o ANTLR .
Até o momento, todas as respostas são fortemente baseadas na personalização do código. A implementação de classes concretas para cada cliente parece-me que, em algum momento no futuro, não será bem dimensionado. A manutenção vai ser cara e dolorosa.
Então, com o Antlr, a idéia é definir seu próprio idioma. Que você pode permitir que usuários (ou desenvolvedores) escrevam regras de negócios nesse idioma.
Tomando seu comentário como exemplo:
Com o seu EL, deve ser possível indicar frases como:
Então...
Você poderia. É uma string, você pode salvá-la como propriedade ou atributo.
Eu não vou mentir. É bem complicado e difícil. É ainda mais difícil se as regras de negócios também forem complicadas.
Aqui estão algumas perguntas de SO que podem ser do seu interesse:
Nota: O ANTLR também gera código para Python e Javascript. Isso pode ajudar a escrever provas de conceito sem muita sobrecarga.
Se você achar que o Antlr é muito difícil, tente com bibliotecas como Expr4J, JEval, Parsii. Isso funciona com um nível mais alto de abstração.
fonte
Você pode pelo menos externalizar o algoritmo para que a classe Customer não precise ser alterada quando um novo cliente for adicionado usando um padrão de design chamado Padrão de Estratégia (é o Gang of Four).
A partir do snippet que você forneceu, é discutível se o padrão de estratégia seria menos sustentável ou mais sustentável, mas pelo menos eliminaria o conhecimento da classe Customer sobre o que precisa ser feito (e eliminaria seu caso de troca).
Um objeto StrategyFactory criaria um ponteiro StrategyIntf (ou referência) com base no CustomerID. O Factory pode retornar uma implementação padrão para clientes que não são especiais.
A classe Customer precisa apenas pedir à fábrica a estratégia correta e depois chamá-la.
Este é um pseudo C ++ muito breve para mostrar o que quero dizer.
A desvantagem dessa solução é que, para cada novo cliente que precise de lógica especial, você precisará criar uma nova implementação do CalculationsStrategyIntf e atualizar a fábrica para devolvê-la aos clientes apropriados. Isso também requer compilação. Mas você pelo menos evitaria o crescente código de espaguete na classe do cliente.
fonte
Crie uma interface com método único e use lamdas em cada classe de implementação. Ou você pode classe anônima para implementar os métodos para diferentes clientes
fonte