Tengo una clase utilizada para procesar pagos de clientes. Todos menos uno de los métodos de esta clase son los mismos para todos los clientes, excepto uno que calcula (por ejemplo) cuánto debe el usuario del cliente. Esto puede variar mucho de un cliente a otro y no hay una manera fácil de capturar la lógica de los cálculos en algo así como un archivo de propiedades, ya que puede haber varios factores personalizados.
Podría escribir código feo que cambie según el ID de cliente:
switch(customerID) {
case 101:
.. do calculations for customer 101
case 102:
.. do calculations for customer 102
case 103:
.. do calculations for customer 103
etc
}
pero esto requiere reconstruir la clase cada vez que tenemos un nuevo cliente. ¿Cuál es la mejor manera?
[Editar] El artículo "duplicado" es completamente diferente. No estoy preguntando cómo evitar una declaración de cambio, estoy preguntando por el diseño moderno que mejor se aplica a este caso, que podría resolver con una declaración de cambio si quisiera escribir un código de dinosaurio. Los ejemplos proporcionados allí son genéricos y no son útiles, ya que esencialmente dicen "Hey, el interruptor funciona bastante bien en algunos casos, no en otros".
[Editar] Decidí ir con la respuesta mejor clasificada (crear una clase "Cliente" separada para cada cliente que implemente una interfaz estándar) por las siguientes razones:
Consistencia: puedo crear una interfaz que garantice que todas las clases de Clientes reciban y devuelvan el mismo resultado, incluso si fueron creadas por otro desarrollador
Mantenibilidad: todo el código está escrito en el mismo lenguaje (Java), por lo que no es necesario que nadie más aprenda un lenguaje de codificación separado para mantener lo que debería ser una característica simple.
Reutilización: en caso de que surja un problema similar en el código, puedo reutilizar la clase Cliente para mantener cualquier número de métodos para implementar la lógica "personalizada".
Familiaridad: ya sé cómo hacer esto, así que puedo hacerlo rápidamente y pasar a otros problemas más urgentes.
Inconvenientes:
Cada nuevo cliente requiere una compilación de la nueva clase de Cliente, lo que puede agregar cierta complejidad a la forma en que compilamos e implementamos cambios.
Un desarrollador debe agregar a cada nuevo cliente; una persona de soporte no puede simplemente agregar la lógica a algo así como un archivo de propiedades. Esto no es ideal ... pero tampoco estaba seguro de cómo una persona de Soporte podría escribir la lógica de negocios necesaria, especialmente si es compleja con muchas excepciones (como es probable).
No escalará bien si agregamos muchos, muchos clientes nuevos. Esto no se espera, pero si sucede, tendremos que repensar muchas otras partes del código además de esta.
Para aquellos de ustedes interesados, pueden usar Java Reflection para llamar a una clase por su nombre:
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);
Respuestas:
Dos opciones me vienen a la mente.
Opción 1: haga de su clase una clase abstracta, donde el método que varía entre los clientes es un método abstracto. Luego cree una subclase para cada cliente.
Opción 2: crear una
Customer
clase o unaICustomer
interfaz que contenga toda la lógica dependiente del cliente. En lugar de tener la clase de procesamiento de pago acepta un ID de cliente, haga que aceptar unaCustomer
oICustomer
objeto. Siempre que necesita hacer algo dependiente del cliente, llama al método apropiado.fuente
Es posible que desee considerar escribir los cálculos personalizados como "complementos" para su aplicación. Luego, usaría un archivo de configuración para indicarle qué programa se debe usar para cada cliente. De esta manera, su aplicación principal no necesitaría ser recompilada para cada nuevo cliente, solo necesita leer (o releer) un archivo de configuración y cargar nuevos complementos.
fuente
Iría con un conjunto de reglas para describir los cálculos. Esto puede mantenerse en cualquier tienda de persistencia y modificarse dinámicamente.
Como alternativa, considere esto:
Donde se
customerOpsIndex
calculó el índice de operación correcto (usted sabe qué cliente necesita qué tratamiento).fuente
Algo como lo siguiente:
observe que todavía tiene la declaración de cambio en el repositorio. Sin embargo, no hay forma de evitar esto, en algún momento debe asignar una identificación de cliente a la lógica requerida. Puede ser inteligente y moverlo a un archivo de configuración, colocar la asignación en un diccionario o cargar dinámicamente en los ensamblados lógicos, pero esencialmente se reduce a cambiar.
fuente
Parece que tiene un mapeo 1 a 1 de clientes a código personalizado y debido a que está utilizando un lenguaje compilado, debe reconstruir su sistema cada vez que obtiene un nuevo cliente
Pruebe con un lenguaje de script incrustado.
Por ejemplo, si su sistema está en Java, podría incrustar JRuby y luego, para cada tienda del cliente, un fragmento correspondiente del código Ruby. Idealmente en algún lugar bajo control de versiones, ya sea en el mismo o en un repositorio de git separado. Y luego evalúe ese fragmento en el contexto de su aplicación Java. JRuby puede llamar a cualquier función Java y acceder a cualquier objeto Java.
Este es un patrón muy común. Por ejemplo, muchos juegos de computadora están escritos en C ++ pero usan scripts de Lua integrados para definir el comportamiento del cliente de cada oponente en el juego.
Por otro lado, si tiene una asignación de muchos a 1 de los clientes al código personalizado, simplemente use el patrón "Estrategia" como ya se sugirió.
Si la asignación no se basa en la identificación del usuario, agregue una
match
función a cada objeto de estrategia y elija una estrategia ordenada de qué estrategia utilizar.Aquí hay un pseudocódigo
fuente
Voy a nadar contra la corriente.
Intentaría implementar mi propio lenguaje de expresión con ANTLR .
Hasta ahora, todas las respuestas están fuertemente basadas en la personalización del código. La implementación de clases concretas para cada cliente me parece que, en algún momento en el futuro, no va a escalar bien. El mantenimiento será costoso y doloroso.
Entonces, con Antlr, la idea es definir tu propio idioma. Que puede permitir que los usuarios (o desarrolladores) escriban reglas comerciales en dicho idioma.
Tomando tu comentario como ejemplo:
Con su EL, debería ser posible para usted enunciar oraciones como:
Luego...
Tú podrías. Es una cadena, puede guardarla como propiedad o atributo.
No mentiré Es bastante complicado y difícil. Es aún más difícil si las reglas de negocio también son complicadas.
Aquí algunas preguntas SO que pueden ser de su interés:
Nota: ANTLR genera código para Python y Javascript también. Eso puede ayudar a escribir pruebas de concepto sin demasiados gastos generales.
Si encuentra que Antlr es demasiado difícil, puede probar con bibliotecas como Expr4J, JEval, Parsii. Estos trabajos con un mayor nivel de abstracción.
fuente
Al menos puede externalizar el algoritmo para que la clase Cliente no necesite cambiar cuando se agrega un nuevo cliente utilizando un patrón de diseño llamado Patrón de Estrategia (está en la Banda de los Cuatro).
Desde el fragmento que proporcionó, es discutible si el patrón de estrategia sería menos mantenible o más mantenible, pero al menos eliminaría el conocimiento de la clase del Cliente sobre lo que debe hacerse (y eliminaría su caso de cambio).
Un objeto StrategyFactory crearía un puntero StrategyIntf (o referencia) basado en el CustomerID. La Fábrica podría devolver una implementación predeterminada para clientes que no son especiales.
La clase Cliente solo necesita preguntarle a la Fábrica la estrategia correcta y luego llamarla.
Este es un breve pseudo C ++ para mostrarle lo que quiero decir.
El inconveniente de esta solución es que, para cada nuevo cliente que necesita una lógica especial, necesitaría crear una nueva implementación de CalculationsStrategyIntf y actualizar la fábrica para devolverla a los clientes apropiados. Esto también requiere compilación. Pero al menos evitaría el código de espagueti cada vez mayor en la clase de clientes.
fuente
Cree una interfaz con un método único y use lamdas en cada clase de implementación. O puede usar una clase anónima para implementar los métodos para diferentes clientes
fuente