patterns method gof oop design-patterns business-objects product

oop - method - ¿Cómo diseñar una entidad comercial genérica y seguir siendo OO?



factory method abap (7)

Estoy trabajando en un producto empaquetado que se supone debe atender a varios clientes con diferentes requisitos (hasta cierto punto) y, como tal, debe estar construido de manera que sea lo suficientemente flexible como para ser personalizado por cada cliente específico. El tipo de personalización del que estamos hablando aquí es que los diferentes clientes pueden tener atributos diferentes para algunos de los objetos comerciales clave. Además, también podrían tener una lógica de negocios diferente vinculada con sus atributos adicionales

Como ejemplo muy simplista: considere "Automóvil" como una entidad comercial en el sistema y, como tal, tiene 4 atributos clave, es decir, Número de Vehículo, Año de Fabricación, Precio y Color.

Es posible que uno de los clientes que usan el sistema agregue 2 atributos más a Automóvil, a saber, ChassisNumber y EngineCapacity. Este cliente necesita alguna lógica de negocios asociada con estos campos para validar que no existe el mismo número de chasis en el sistema cuando se agrega un nuevo Automóvil.

Otro cliente solo necesita un atributo adicional llamado SaleDate. SaleDate tiene su propia verificación de lógica de negocios que valida si el vehículo no existe en algunos registros policiales como un vehículo robado cuando se ingresa la fecha de venta

La mayor parte de mi experiencia ha sido principalmente en la creación de aplicaciones empresariales para un solo cliente y estoy realmente luchando por ver cómo puedo manejar una entidad comercial cuyos atributos son dinámicos y también tiene la capacidad de tener una lógica empresarial dinámica en un paradigma orientado a objetos.

Cuestiones clave

  • ¿Hay algún principio / patrón general de OO que me ayude a abordar este tipo de diseño?

Estoy seguro de que las personas que han trabajado en productos genéricos / empaquetados habrían enfrentado escenarios similares en la mayoría de ellos. Cualquier consejo / punteros / orientación general también se agradece.

Mi tecnología es .NET 3.5 / C # y el proyecto tiene una arquitectura en capas con una capa empresarial que consta de entidades comerciales que abarcan su lógica empresarial


Desarrollamos un SDK que hace algo como esto. Elegimos COM para nuestro núcleo porque estábamos mucho más cómodos con él que con .NET de bajo nivel, pero sin duda usted podría hacerlo todo de forma nativa en .NET.

La arquitectura básica es algo así: los tipos se describen en una biblioteca de tipos COM. Todos los tipos se derivan de un tipo de raíz llamado Objeto. Una DLL COM implementa este tipo de objeto raíz y proporciona acceso genérico a las propiedades de los tipos derivados a través de IDispatch. Esta DLL está envuelta en un ensamblaje de .NET PIA porque anticipamos que la mayoría de los desarrolladores preferirán trabajar en .NET. El tipo de objeto tiene un método de fábrica para crear objetos de cualquier tipo en el modelo.

Nuestro producto se encuentra en la versión 1 y aún no hemos implementado métodos. En esta versión, la lógica empresarial debe estar codificada en la aplicación cliente. Pero nuestra visión general es que los métodos serán escritos por el desarrollador en su idioma de elección, compilados en ensamblados .NET o DLL COM (y quizás también en Java) y expuestos a través de IDispatch. Luego, la misma implementación de IDispatch en nuestro tipo de objeto raíz puede llamarlos.

Si anticipa que la mayoría de la lógica empresarial personalizada será la validación (como la comprobación de números de chasis duplicados), entonces podría implementar algunos eventos generales en su tipo de objeto raíz (suponiendo que lo hizo de la misma manera que nosotros). Nuestro tipo de objeto dispara un evento cada vez que se actualiza una propiedad, y supongo que esto podría aumentarse con un método de validación que se llama automáticamente si se define uno.

Se necesita mucho trabajo para crear un sistema genérico como este, pero la recompensa es que el desarrollo de aplicaciones sobre el SDK es muy rápido.

Usted dice que sus clientes deberían poder agregar propiedades personalizadas e implementar la lógica empresarial ellos mismos "sin programación". Si su sistema también implementa almacenamiento de datos en función de los tipos (el nuestro lo hace), el cliente podría agregar propiedades sin programación, editando el modelo (proporcionamos un editor de modelos de GUI). Incluso podría proporcionar una aplicación de usuario genérica que presente de forma dinámica Los controles de ingreso de datos dependen de los tipos, para que sus clientes puedan capturar datos personalizados sin programación adicional. (Proporcionamos una aplicación de cliente genérica, pero es más una herramienta de desarrollador que una aplicación de usuario final viable). No veo cómo podría permitir que sus clientes implementen lógica personalizada sin programación ... a menos que desee proporcionar algún tipo de arrastrar y soltar generador de flujo de trabajo GUI ... seguramente una tarea enorme.

No prevemos que los usuarios de negocios hagan nada de esto. En nuestro modelo de desarrollo, toda la personalización es realizada por un desarrollador, pero no necesariamente costosa. Parte de nuestra visión es permitir que los desarrolladores menos experimentados produzcan aplicaciones empresariales sólidas.


Dos enfoques es lo que siento:

1) Si diferentes clientes caen en el mismo dominio (como Fabricación / Finanzas), entonces es mejor diseñar objetos de forma que BaseObject tenga atributos que sean muy comunes y otros que puedan variar entre clientes como pares clave-valor. Además, intente implementar un motor de reglas como IBM ILog (http://www-01.ibm.com/software/integration/business-rule-management/rulesnet-family/about/).

2) Lenguaje de marcado de modelo predictivo (http://en.wikipedia.org/wiki/PMML)


Eso es un modelo de objeto dinámico o modelo de objeto adaptativo que estás construyendo. Y, por supuesto, cuando los clientes comienzan a agregar comportamiento y datos, están programando, por lo que necesita tener control de versiones, pruebas, lanzamiento, espacio de nombres / contexto y administración de derechos para eso.


Este es uno de nuestros mayores desafíos, ya que tenemos varios clientes que utilizan la misma base de código, pero tienen necesidades muy diferentes. Permítanme compartir nuestra historia de la evolución con ustedes:

Nuestra compañía comenzó con un solo cliente, y cuando comenzamos a conseguir otros clientes, comenzamos a ver cosas como estas en el código:

if(clientName == "ABC") { // do it the way ABC client likes } else { // do it the way most clients like. }

Finalmente, nos dimos cuenta del hecho de que esto hace que el código sea realmente feo e inmanejable. Si otro cliente quisiera que los suyos se comportaran como los de ABC en un lugar y los de CBA en otro lugar, estaríamos estancados. Así que en lugar de eso, recurrimos a un archivo .properties con un montón de puntos de configuración.

if((bool)configProps.get("LastNameFirst")) { // output the last name first } else { // output the first name first }

Esto fue una mejora, pero aún muy torpe. "Las cuerdas mágicas" abundaron. No había ninguna organización o documentación real en torno a las diversas propiedades. Muchas de las propiedades dependían de otras propiedades y no harían nada (¡o incluso romperían algo!) Si no se usaran en las combinaciones correctas. Mucho (posiblemente la mayoría) de nuestro tiempo en algunas iteraciones lo pasamos reparando errores que surgieron porque habíamos "arreglado" algo para un cliente que rompió la configuración de otro cliente. Cuando obtuviéramos un nuevo cliente, comenzaríamos con el archivo de propiedades de otro cliente que tenía la configuración "más parecida" a la que este cliente quería, y luego trataríamos de modificar las cosas hasta que se vieran bien.

Intentamos usar varias técnicas para lograr que estos puntos de configuración fueran menos complejos, pero solo hicimos un progreso moderado:

if(userDisplayConfigBean.showLastNameFirst())) { // output the last name first } else { // output the first name first }

Hubo algunos proyectos para obtener estas configuraciones bajo control. Uno implicaba escribir un motor de visualización basado en XML para poder personalizar mejor las pantallas para cada cliente.

<client name="ABC"> <field name="last_name" /> <field name="first_name" /> </client>

Otro proyecto consistió en escribir un sistema de administración de la configuración para consolidar nuestro código de configuración, hacer que cada punto de configuración esté bien documentado, permitir que los superusuarios cambien los valores de configuración en tiempo de ejecución y permitir que el código valide cada cambio para evitar obtener una combinación no válida de valores de configuracion.

Estos diversos cambios definitivamente hicieron la vida mucho más fácil con cada nuevo cliente, pero la mayoría de ellos no pudieron abordar la raíz de nuestros problemas. El cambio que realmente nos benefició más fue cuando dejamos de ver nuestro producto como una serie de soluciones para hacer que algo funcionara para un cliente más, y comenzamos a ver nuestro producto como un "producto". Cuando un cliente solicitó una nueva función, comenzamos a considerar cuidadosamente preguntas como:

  • ¿Cuántos otros clientes podrían usar esta función, ya sea ahora o en el futuro?
  • ¿Se puede implementar de una manera que no haga que nuestro código sea menos manejable?
  • ¿Podríamos implementar una función diferente que solicitan, que aún satisfaga sus necesidades y que sea más adecuada para que otros clientes la reutilicen?

Al implementar una característica, tomaríamos la vista larga. En lugar de crear un nuevo campo de base de datos que solo usaría un cliente, podríamos crear una tabla completamente nueva que permita a cualquier cliente definir cualquier número de campos personalizados. Se necesitaría más trabajo por adelantado, pero podríamos permitir que cada cliente personalice su propio producto con un alto grado de flexibilidad, sin necesidad de que un programador cambie ningún código.

Dicho esto, a veces hay ciertas personalizaciones que realmente no se pueden lograr sin invertir un enorme esfuerzo en complejos motores de Reglas, etc. Cuando solo necesita hacer que funcione de una manera para un cliente y otra para otro cliente, he encontrado que lo mejor es programar para interfaces y aprovechar la inyección de dependencia . Si sigue los principios "SÓLIDOS" para asegurarse de que su código esté escrito de manera modular con una buena "separación de preocupaciones", etc., no es tan doloroso cambiar la implementación de una parte particular de su código para un cliente en particular:

public FirstLastNameGenerator : INameDisplayGenerator { IPersonRepository _personRepository; public FirstLastNameGenerator(IPersonRepository personRepository) { _personRepository = personRepository; } public string GenerateDisplayNameForPerson(int personId) { Person person = _personRepository.GetById(personId); return person.FirstName + " " + person.LastName; } } public AbcModule : NinjectModule { public override void Load() { Rebind<INameDisplayGenerator>().To<FirstLastNameGenerator>(); } }

Este enfoque se ve reforzado por las otras técnicas que mencioné anteriormente. Por ejemplo, no escribí un AbcNameGenerator porque quizás otros clientes quieran un comportamiento similar en sus programas. Pero utilizando este enfoque, puede definir con bastante facilidad los módulos que anulan la configuración predeterminada para clientes específicos, de una manera que es muy flexible y extensible.

Debido a que los sistemas como este son intrínsecamente frágiles, también es importante concentrarse en las pruebas automatizadas: pruebas unitarias para clases individuales, pruebas de integración para asegurarse (por ejemplo) de que todos los enlaces de inyección funcionan correctamente y pruebas del sistema para asegurarse de que todo Trabajan juntos sin retroceder.

PD: uso "nosotros" a lo largo de esta historia, aunque no estuve trabajando en la empresa durante gran parte de su historia.

PPS: Perdona la mezcla de C # y Java.


Sé que su pregunta es general, no está ligada a una tecnología, pero como menciona que realmente trabaja con .NET, le sugiero que vea una nueva y muy importante pieza tecnológica que forma parte de .NET 4: el tipo "dinámico".

También hay un buen artículo en CodeProject aquí: DynamicObjects - Duck-Typing en .NET .

Probablemente valga la pena mirar, porque, si tengo que implementar el sistema dinámico que describe, sin duda intentaría implementar mis entidades basadas en la clase DynamicObject y agregar propiedades y métodos personalizados utilizando los métodos TryGetxxx. También depende de si está enfocado en el tiempo de compilación o en el tiempo de ejecución. Aquí hay un enlace interesante aquí en SO: Agregar miembros dinámicamente a un objeto dinámico sobre este tema.


Una forma de abordar esto es utilizar una meta-capa, una reflexión o ambas. Además, deberá proporcionar una aplicación de personalización que permita la modificación, por parte de los usuarios, de su capa de lógica empresarial. Tal metacapa realmente no encaja en su arquitectura en capas, es más como un ortoganal de capas para su arquitectura existente, aunque la aplicación en ejecución probablemente necesitará referirse a ella, al menos en la inicialización. Este tipo de instalación es probablemente una de las maneras más rápidas de arruinar la aplicación de producción conocida por el hombre, por lo que debe:

  1. Asegúrese de que el acceso a este editor esté limitado a personas con un alto nivel de derechos en el sistema (por ejemplo, administrador).
  2. Proporcione un área de espacio de pruebas para que las modificaciones del cliente se prueben antes de que cualquier cambio que están probando se coloque en el sistema de producción.
  3. Un servicio "OOPS" mediante el cual pueden revertir su sistema de producción a su valor predeterminado inicial proporcionado, o a la última revisión antes del cambio.
  4. Su meta-capa debe estar muy bien especificada para que el rango de actividades esté bien definido: "Lo que no está específicamente permitido, de George Orwell está prohibido".

Su meta-capa tendrá objetos como Objeto de negocio, Método, Propiedad y eventos como Agregar objeto de negocio, Método de llamada, etc.

Existe una gran cantidad de información sobre la meta-programación disponible en la web, pero me gustaría comenzar con Pattern Languages ​​of Program Design Vol 2 o cualquiera de los recursos de WWW relacionados o emanados de Kent o Coplien.


Diseñar un modelo central que actúe como un proyecto independiente.

Aquí hay una lista de algunos posibles requisitos básicos ...

El diseño del núcleo contendría:

  • Clases que funcionan (y posiblemente se extiendan) en todos los subproyectos.
  • herramientas más complejas como las interacciones de la base de datos (a menos que sean específicas del proyecto)
  • una estructura de configuración general que debe considerarse estándar en todos los proyectos

Luego, todos los proyectos posteriores que se personalizan por cliente se consideran extensiones de este proyecto central.

Lo que estás describiendo es el propósito básico de cualquier Framework. Es decir, cree un conjunto de funciones básicas que se pueden diferenciar de la totalidad para que no tenga que duplicar ese esfuerzo de desarrollo en cada proyecto que cree. Es decir, dejar en un marco y la mitad de su trabajo ya está hecho.

Podría decir, "¿qué pasa con el SCM (Software Configuration Management)?"

¿Cómo hace un seguimiento del historial de revisiones de todos los subproyectos sin incluir el núcleo en el repositorio de subproyectos?

Afortunadamente, este es un viejo problema. Muchos proyectos de software, especialmente aquellos en el mundo de linux / código abierto, hacen un uso extensivo de bibliotecas y complementos externos.

De hecho, git tiene un comando que se usa específicamente para importar un repositorio de proyecto a otro como sub-repositorio (preservando todo el historial de revisión del sub-repositorio, etc.). De hecho, no puede modificar el contenido del sub-repositorio porque el proyecto no hará un seguimiento de su historial.

El comando del que estoy hablando se llama '' submódulo de git ''.

Puede preguntar, "¿qué pasa si desarrollo una característica realmente interesante en el proyecto de un cliente que me gustaría usar en todos los proyectos de mi cliente?".

Simplemente agregue esa característica al núcleo y ejecute un ''git submodule sync'' en todos los demás proyectos. La forma en que funciona git submodule es que apunta a un compromiso específico dentro del árbol de historia del sub-repositorio. Por lo tanto, cuando ese árbol se cambia en sentido ascendente, debe retirar esos cambios en sentido descendente a los proyectos en los que se utilizan.

La estructura para implementar tal cosa funcionaría así. Digamos que su software está escrito específicamente para gestionar un concesionario de automóviles (inventario, ventas, empleados, clientes, pedidos, etc.). Crea un módulo central que cubre todas estas características porque se espera que se usen en el software para todos sus clientes.

Sin embargo, recientemente ha adquirido un nuevo cliente que desea ser más experto en tecnología al agregar ventas en línea a su concesionario. Por supuesto, su sitio web está diseñado por un equipo separado de desarrolladores / diseñadores web y webmaster, pero quieren una API web (es decir, capa de servicio) para aprovechar la infraestructura actual de su sitio web.

Lo que harías es crear un proyecto para el cliente, lo llamaremos WebDealersRUs y vincularemos el submódulo central al repositorio.

El beneficio oculto de esto es que, una vez que comienza a verse como una base de código como piezas conectables, puede comenzar a diseñarlas desde el principio como piezas modulares que se pueden colocar en un proyecto con muy poco esfuerzo.

Considere el ejemplo anterior. Digamos que su base de clientes está empezando a ver los méritos de agregar un frente web para aumentar las ventas. Simplemente extraiga la API web de las WebDealersRUs en su propio repositorio y conéctela nuevamente como un submódulo. Luego propaga a todos tus clientes que lo deseen.

Lo que obtienes es una gran recompensa con un mínimo esfuerzo.

Por supuesto, siempre habrá partes de cada proyecto que sean específicas del cliente (marca, etc.). Es por eso que cada cliente debe tener un repositorio separado que contenga su versión única del software. Pero eso no significa que no pueda sacar partes y generalizarlas para volver a utilizarlas en proyectos posteriores.

Aunque me acerco a este problema desde el nivel macro, se puede aplicar a partes más pequeñas / más específicas del código base. La clave aquí es el código que desea reutilizar y necesita ser genérico .

La POO entra en juego aquí porque: donde la funcionalidad se implementa en el núcleo pero se extiende en el código del cliente, se usará una clase base y se heredará de ella; donde se espera que la funcionalidad devuelva un tipo de resultado similar, pero las implementaciones de esa funcionalidad pueden ser muy diferentes en todas las clases (es decir, no hay una jerarquía de herencia directa) es mejor usar una interfaz para imponer esa relación.