Você quer saber qual é a melhor estratégia, a solução BOA mesmo?
Procure melhorar a arquitetura para ter um único código fonte.
Seria mais ou menos assim: as partes customizáveis implementadas como se fossem módulos “plugáveis”, mas tudo fazendo parte do mesmo projeto.
Vou colocar um exemplo aqui, eu sei que no sistema real as coisas devem ser bem mais complexas, mas é só para pegar a idéia:
1- Partes diferenciadas no código-fonte:
Imaginemos um método que calcula os acréscimos para um pagamento atrasado.
BigDecimal calcularValorPagamento(Pagamento p, Date dataPagamento) {
int diasAtraso = calcularDiferencaDias(dataPagamento, p.getDataVencimento());
BigDecimal taxaDiariaAtraso = dao.consultarTaxaAtraso();
BigDecimal valor = p.getValorPrincipal() + dao.consultarMultaAtraso() + diasAtraso*taxaDiaraAtraso;
return valor;
}
Mas o cliente XXX funciona um pouco diferente: a multa está sempre variando, e deve ser consultada em um webservice. A taxa tambem vem desse webservice, e é por mês de atraso e não por dia.
Aí você cria uma versão diferenciada para este cliente:
BigDecimal calcularValorPagamento(Pagamento p, Date dataPagamento) {
int mesesAtraso = calcularDiferencaMeses(dataPagamento, p.getDataVencimento());
WebServiceResponse response = webserviceClient.consultarTaxasEMultas();
BigDecimal valor = p.getValorPrincipal() + response.getMulta() + meses*response.getTaxaMensal();
return valor;
}
Essa é uma das classes que usam essa rotina:
class PagamentoFatura {
void confirmarPagamento() {
// ....
BigDecimal valorAPagar = calcularValorPagamento(pagamento, new Date());
if (valorAPagar != valorPago) {
// ....
}
// ....
}
}
Você substituiria por uma estratégia diferenciada para cada cliente:
// Interface para estratégia de cálculo
interface CalculadorPagamento {
BigDecimal calcularValorPagamento(Pagamento p, Date dataPagamento);
}
// Implementação básica
class CalculadorPagamentoBase {
BigDecimal calcularValorPagamento(Pagamento p, Date dataPagamento) {
int diasAtraso = calcularDiferencaDias(dataPagamento, p.getDataVencimento());
BigDecimal taxaDiariaAtraso = dao.consultarTaxaAtraso();
BigDecimal valor = p.getValorPrincipal() + dao.consultarMultaAtraso() + diasAtraso*taxaDiaraAtraso;
return valor;
}
}
// Implementação com lógica específica do cliente
class CalculadorPagamentoBufunfaBank {
BigDecimal calcularValorPagamento(Pagamento p, Date dataPagamento) {
int mesesAtraso = calcularDiferencaMeses(dataPagamento, p.getDataVencimento());
WebServiceResponse response = webserviceClient.consultarTaxasEMultas();
BigDecimal valor = p.getValorPrincipal() + response.getMulta() + meses*response.getTaxaMensal();
return valor;
}
}
// A classe que conhece qual estratégia usar para cada cliente.
// Devolve a instância correta dependendo da empresa que está utilizando.
class CalculadorPagamentoFactory {
static CalculadorPagamento getCalculadorPagamento(Empresa empresa) {
if (empresa.getId().equals(ID_BUFUNFA_BANK)) {
return new CalculadorPagamentoBufunfaBank();
} else {
return new CalculadorPagamentoBase();
}
}
}
// Classe utilizadora (alterada para usar a estratégia plugável)
class PagamentoFatura {
void confirmarPagamento() {
// ....
CalculadorPagamento calculador = CalculadorPagamentoFactory.getCalculadorPagamento(empresa);
BigDecimal valorAPagar = calculador.calcularValorPagamento(pagamento, new Date());
if (valorAPagar != valorPago) {
// ....
}
// ....
}
}
E dessa forma você tem todas as rotinas convivendo no mesmo projeto, mas é capaz de selecionar a execução apropriada em tempo de execução.
Como eu disse, é só um rascunho pra tentar transmitir a idéia.
Procure sobre alguns design patterns como Strategy, Abstract Factory, eles vão ajudar a pensar em como fazer.
2- Interface Gráfica e recursos
Você não mencionou se o sistema é desktop ou web… Se for desktop, a interface de usuário é código Java e portanto pode seguir a mesma estratégia usada no restante do código. Se for web, pode usar a substituição em tempo de deploy.
Exemplo:
Seu site tem as páginas, o CSS, as imagens.
A solução mais óbvia seria ter um projeto diferente para guardar as personalizações de cada cliente, sempre na estrutura padrão:
/web
|
|_____pages
|
|_____resources
|_______css
|_______images
Mas você pode guardar as customizações no mesmo projeto, em diretórios diferentes:
/web
|
|_____pages
|
|_____resources
|_______css
|_______images
/layout_bar_do_ze
|
|_____resources
|_______css
|_______images
/layout_banco_bufunfa
|
|_____resources
|_______css
|_______images
Nesse caso, o seu script de deploy seria responsável por substituir os arquivos customizados para cada cliente, sobrescrevendo a estrutura principal.
Essa mesma técnica vale para os recursos estáticos de uma aplicação desktop (por exemplo, imagens ou arquivos de configuração)
Conclusão:
Isso pode dar um trabalho enorme, eu não faço idéia da complexidade que pode estar o seu sistema, e nem se ele está razoavelmente modularizado e fácil de manter.
Mas apesar de tudo vale a pena. Substituir partes manualmente para cada cliente a cada release é um custo muito grande, e um risco maior ainda de algo dar errada. Fuja dessa opção como o diabo foge da cruz!