Uso del marco SysExtension para determinar qué subclase crear en Dynamics AX 2012
Publicado: 16 de febrero de 2025, 0:25:38 UTC
Este artículo describe cómo utilizar el marco SysExtension poco conocido en Dynamics AX 2012 y Dynamics 365 for Operations para crear instancias de subclases basadas en decoraciones de atributos, lo que permite un diseño fácilmente extensible de una jerarquía de clases de procesamiento.
Using the SysExtension Framework to Find Out Which Subclass to Instantiate in Dynamics AX 2012
La información de este artículo se basa en Dynamics AX 2012 R3. Puede que sea válida o no para otras versiones. (Actualización: puedo confirmar que la información de este artículo también es válida para Dynamics 365 for Operations)
Al implementar clases de procesamiento en Dynamics AX, a menudo se enfrenta a la creación de una jerarquía de clases en la que cada subclase corresponde a un valor de enumeración o tiene algún otro acoplamiento de datos. Un diseño clásico es tener un método de construcción en la superclase, que tiene un conmutador que determina qué clase instanciar en función de la entrada.
Esto funciona bien en principio, pero si tiene muchas entradas posibles diferentes (muchos elementos en una enumeración o quizás la entrada es una combinación de varios valores diferentes), puede volverse tedioso y propenso a errores de mantener y el diseño siempre tiene la desventaja de que necesitará modificar dicho método de construcción si alguna vez agrega una nueva subclase o realiza cambios en qué subclase debe usarse en función de qué entrada.
Afortunadamente, existe una forma mucho más elegante, aunque lamentablemente también mucho menos conocida, de hacerlo: mediante el uso del marco SysExtension.
Este marco aprovecha los atributos que puedes usar para decorar tus subclases y hacer que el sistema pueda determinar qué subclase debe usarse para cada tarea. Aún necesitarás un método de construcción, pero si lo haces correctamente, nunca tendrás que modificarlo al agregar nuevas subclases.
Veamos un ejemplo imaginario y digamos que va a implementar una jerarquía que realiza algún tipo de procesamiento basado en la tabla InventTrans. El procesamiento que se debe realizar depende del StatusReceipt y StatusIssue de los registros, así como de si los registros están relacionados con SalesLine, PurchLine o ninguno de ellos. En este momento, ya está viendo muchas combinaciones diferentes.
Digamos entonces que sabes que por ahora solo necesitas manejar un puñado de combinaciones, pero también sabes que se te pedirá que seas capaz de manejar más y más combinaciones con el tiempo.
Mantengámoslo relativamente simple y digamos que por ahora solo necesita manejar registros relacionados con SalesLine con un StatusIssue de ReservPhysical o ReservOrdered, todas las demás combinaciones se pueden ignorar por ahora, pero como sabe que tendrá que manejarlas más adelante, querrá diseñar su código de una manera que lo haga fácilmente extensible.
Por ahora tu jerarquía podría verse así:
- Mi procesador
- MiProcesador_Ventas
- MiProcesador_Ventas_ReservaOrdenada
- MiProcesador_Ventas_ReservaPhysical
- MiProcesador_Ventas
Ahora, podría implementar fácilmente un método en la superclase que cree una instancia de una subclase basada en una enumeración ModuleInventPurchSales y StatusIssue. Pero entonces necesitará modificar la superclase cada vez que agregue una subclase, y esa no es realmente la idea de herencia en la programación orientada a objetos. Después de todo, no necesita modificar RunBaseBatch o SysOperationServiceBase cada vez que agregue un nuevo trabajo por lotes.
En su lugar, puede utilizar el marco SysExtension. Para ello, deberá agregar otra clase que debe extender SysAttribute. Esta clase se utilizará como el atributo con el que puede decorar sus clases de procesamiento.
Esta clase es muy similar a cómo crearías una clase de contrato de datos para una implementación de SysOperation, ya que tendrá algunos miembros de datos y métodos parm para obtener y configurar esos valores.
En nuestro caso, la ClassDeclaration podría verse así:
{
ModuleInventPurchSales module;
StatusIssue statusIssue;
StatusReceipt statusReceipt
}
Debe crear un método new() para instanciar todos los miembros de datos. Si lo desea, puede asignarles valores predeterminados a algunos o a todos ellos, pero no lo he hecho.
StatusIssue _statusIssue,
StatusReceipt _statusReceipt)
{
;
super();
module = _module;
statusIssue = _statusIssue;
statusReceipt = _statusReceipt;
}
También deberías implementar un método parm para cada miembro de datos, pero los he omitido aquí porque estoy seguro de que sabes cómo hacerlo; de lo contrario, considerémoslo un ejercicio ;-)
Ahora puede utilizar su clase de atributo para decorar cada una de sus clases de procesamiento. Por ejemplo, las declaraciones de clase podrían verse así:
StatusIssue::None,
StatusReceipt::None)]
class MyProcessor_Sales extends MyProcessor
{
}
[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
StatusIssue::ReservOrdered,
StatusReceipt::None)]
class MyProcessor_Sales_ReservOrdered extends MyProcessor_Sales
{
}
[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
StatusIssue::ReservPhysical,
StatusReceipt::None)]
class MyProcessor_Sales_ReservPhysical extends MyProcessor_Sales
{
}
Por supuesto, puede nombrar sus clases de la forma que desee, lo importante aquí es que decore sus clases con atributos que correspondan al tipo de procesamiento que realizan. (Pero tenga en cuenta que existen convenciones de nomenclatura para las jerarquías de clases en Dynamics AX y siempre es una buena idea seguirlas, si es posible).
Ahora que ha decorado sus clases para identificar qué tipo de procesamiento realiza cada una de ellas, puede aprovechar el marco SysExtension para crear instancias de objetos de las subclases según sea necesario.
En su superclase (MyProcessor), podría agregar un método de construcción como este:
StatusIssue _statusIssue,
StatusReceipt _statusReceipt)
{
MyProcessor ret;
MyProcessorSystemAttribute attribute;
;
attribute = new MyProcessorSystemAttribute( _module,
_statusIssue,
_statusReceipt);
ret = SysExtensionAppClassFactory::getClassFromSysAttribute(classStr(MyProcessor), attribute);
if (!ret)
{
// no class found
// here you could throw an error, instantiate a default
// processor instead, or just do nothing, up to you
}
return ret;
}
La parte realmente interesante -y en realidad el objeto (perdón por el juego de palabras) de todo este artículo- es el método getClassFromSysAttribute() de la clase SysExtensionAppClassFactory. Lo que hace este método es aceptar el nombre de la superclase de una jerarquía (y esta superclase no necesita estar en la parte superior de la jerarquía; simplemente significa que solo las clases que extiendan esta clase serán elegibles) y un objeto de atributo.
Luego devuelve un objeto de una clase que extiende la superclase especificada y está decorado con un atributo correspondiente.
Obviamente, puedes agregar tanta validación o lógica adicional al método de construcción como desees, pero lo importante aquí es que, una vez implementado, nunca deberías tener que modificar este método nuevamente. Puedes agregar subclases a la jerarquía y, siempre que te asegures de decorarlas adecuadamente, el método de construcción las encontrará incluso si no existían cuando se escribió.
¿Qué ocurre con el rendimiento? Sinceramente, no he intentado compararlo, pero tengo la sensación de que probablemente tenga un rendimiento peor que el diseño clásico de la sentencia switch. Sin embargo, teniendo en cuenta que, con diferencia, la mayoría de los problemas de rendimiento en Dynamics AX se deben al acceso a la base de datos, no me preocuparía demasiado por ello.
Por supuesto, si está implementando algo que requerirá la creación rápida de miles de objetos, es posible que desee investigar más, pero en los casos clásicos en los que simplemente instancia un solo objeto para realizar un procesamiento prolongado, dudo que importe. Además, teniendo en cuenta mi consejo de resolución de problemas (próximo párrafo), parece que el marco SysExtension depende del almacenamiento en caché, por lo que en un sistema en ejecución dudo que tenga un impacto significativo en el rendimiento. Solución de problemas: si el método de construcción no encuentra sus subclases a pesar de que está seguro de que están decoradas correctamente, puede ser un problema de almacenamiento en caché. Intente borrar las cachés tanto en el cliente como en el servidor. No debería ser necesario reiniciar el AOS, pero puede ser el último recurso.