Miklix

Utilização do SysExtension Framework para descobrir qual a subclasse a instanciar no Dynamics AX 2012

Publicado: 16 de fevereiro de 2025 às 00:25:55 UTC

Este artigo descreve como utilizar a estrutura SysExtension pouco conhecida no Dynamics AX 2012 e no Dynamics 365 for Operations para instanciar subclasses com base em decorações de atributos, permitindo um design facilmente extensível de uma hierarquia de classes de processamento.


Esta página foi traduzida automaticamente do inglês para a tornar acessível ao maior número possível de pessoas. Infelizmente, a tradução automática ainda não é uma tecnologia aperfeiçoada, pelo que podem ocorrer erros. Se preferir, pode ver a versão original em inglês aqui:

Using the SysExtension Framework to Find Out Which Subclass to Instantiate in Dynamics AX 2012

As informações neste post são baseadas no Dynamics AX 2012 R3. Pode ou não ser válido para outras versões. (Atualização: Posso confirmar que a informação neste artigo também é válida para o Dynamics 365 for Operations)

Ao implementar classes de processamento no Dynamics AX, depara-se frequentemente com a criação de uma hierarquia de classes em que cada subclasse corresponde a um valor de enumeração ou tem algum outro acoplamento de dados. Um design clássico é ter um método de construção na superclasse, que tem uma opção que determina qual a classe a instanciar com base na entrada.

Isto funciona bem em princípio, mas se tiver muitas entradas possíveis diferentes (muitos elementos numa enumeração ou talvez a entrada seja uma combinação de vários valores diferentes), pode tornar-se tedioso e propenso a erros de manutenção, e o design tem sempre a desvantagem de que terá de modificar o método de construção se adicionar uma nova subclasse ou fizer alterações em que subclasse deve ser utilizada com base em qual entrada.

Felizmente, existe uma forma muito mais elegante, mas infelizmente também muito menos conhecida, de o fazer, ou seja, utilizando a estrutura SysExtension.

Esta estrutura aproveita atributos que pode utilizar para decorar as suas subclasses para que o sistema consiga descobrir qual a subclasse que deve ser utilizada para manipular o quê. Continuará a precisar de um método de construção, mas se for feito corretamente, nunca precisará de o modificar ao adicionar novas subclasses.

Vamos analisar um exemplo imaginário e dizer que vai implementar uma hierarquia que faz algum tipo de processamento com base na tabela InventTrans. O processamento a ser feito depende do StatusReceipt e StatusIssue dos registos, bem como se os registos estão relacionados com SalesLine, PurchLine ou nenhum deles. Agora já está a ver muitas combinações diferentes.

Digamos então que sabe que por enquanto só tem de lidar com algumas combinações, mas também sabe que lhe será pedido que consiga lidar com cada vez mais combinações ao longo do tempo.

Vamos manter as coisas relativamente simples e dizer que, por enquanto, só precisa de manipular registos relacionados com SalesLine com um StatusIssue de ReservPhysical ou ReservOrdered.

A sua hierarquia pode ficar parecida com esta por enquanto:

  • MeuProcessador
    • MeuProcessador_Vendas
      • MyProcessor_Sales_ReservOrdered
      • MyProcessor_Sales_ReservPhysical

Agora, pode facilmente implementar um método na superclasse que instancia uma subclasse com base num ModuleInventPurchSales e numa enumeração StatusIssue. Mas terá de modificar a superclasse sempre que adicionar uma subclasse, e esta não é realmente a ideia de herança na programação orientada a objectos. Afinal, não precisa de modificar o RunBaseBatch ou o SysOperationServiceBase sempre que adicionar um novo trabalho em lote.

Em vez disso, pode utilizar a estrutura SysExtension. Isto exigirá que adicione outra classe, que precisa de estender SysAttribute. Esta classe será utilizada como o atributo com o qual pode decorar as suas classes de processamento.

Esta classe é muito semelhante à forma como se criaria uma classe de contrato de dados para uma implementação SysOperation, uma vez que terá alguns membros de dados e métodos de parâmetro para obter e definir estes valores.

No nosso caso, a ClassDeclaration pode ser semelhante a isto:

class MyProcessorSystemAttribute extends SysAttribute
{
    ModuleInventPurchSales  module;
    StatusIssue             statusIssue;
    StatusReceipt           statusReceipt
}

É necessário criar um método new() para instanciar todos os membros de dados. Se desejar, pode dar valores padrão a alguns ou a todos eles, mas eu não o fiz.

public void new(ModuleInventPurchSales  _module,
                StatusIssue             _statusIssue,
                StatusReceipt           _statusReceipt)
{
    ;

    super();

    module          = _module;
    statusIssue     = _statusIssue;
    statusReceipt   = _statusReceipt;
}

E também deve implementar um método parm para cada membro de dados, mas omiti estes aqui porque tenho a certeza de que sabe como o fazer - caso contrário, vamos considerar isto um exercício ;-)

Agora pode utilizar a sua classe de atributos para decorar cada uma das suas classes de processamento. Por exemplo, as declarações de classe poderiam ser assim:

[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
                            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
{
}

Claro que pode nomear as suas classes da forma que desejar. O importante aqui é decorar as suas classes com atributos que correspondam ao tipo de processamento que fazem. (Mas tenha em mente que existem convenções de nomenclatura para hierarquias de classes no Dynamics AX e é sempre uma boa ideia segui-las, se possível).

Agora que já decorou as suas classes para identificar que tipo de processamento cada uma delas faz, pode tirar partido da estrutura SysExtension para instanciar os objetos das subclasses, conforme necessário.

Na sua superclasse (MyProcessor), pode adicionar um método de construção como este:

public static MyProcessor construct(ModuleInventPurchSales _module,
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;
}

A parte realmente interessante - e realmente o objecto (com o perdão do trocadilho) de todo este post - é o método getClassFromSysAttribute() na classe SysExtensionAppClassFactory. O que este método faz é aceitar o nome da superclasse de uma hierarquia (e essa superclasse não tem de estar no topo da hierarquia; isto significa simplesmente que apenas as classes que estendem essa classe serão elegíveis) e um objeto de atributo.

De seguida, devolve um objeto de uma classe que estende a superclasse especificada e é decorada com um atributo correspondente.

Obviamente, pode adicionar a validação ou a lógica que desejar ao método de construção, mas o mais importante aqui é que, uma vez implementado, nunca mais terá de modificar este método. Pode adicionar subclasses à hierarquia e, desde que as decore adequadamente, o método de construção irá encontrá-las, mesmo que não existissem quando foram escritas.

E quanto ao desempenho? Sinceramente, não tentei fazer um benchmark, mas o meu pressentimento é que isto provavelmente tem um desempenho inferior ao design clássico da instrução switch. No entanto, considerando que a maioria dos problemas de desempenho no Dynamics AX são causados pelo acesso à base de dados, não me preocuparia muito com isso.

Claro que se estiver a implementar algo que exigirá a criação rápida de milhares de objetos, poderá ter de investigar mais a fundo, mas nos casos clássicos em que apenas instancia um único objeto para fazer um processamento demorado, duvido que isso faça alguma diferença. Além disso, considerando a minha dica de resolução de problemas (próximo parágrafo), parece que a estrutura SysExtension depende da cache, pelo que, num sistema em execução, duvido que haja um impacto significativo no desempenho Resolução de problemas: se o método de construção não encontrar as suas subclasses, mesmo que tenha a certeza de que estão decoradas correctamente, pode ser um problema de cache. Tente limpar as caches no cliente e no servidor. Não deverá ser necessário reiniciar o AOS, mas pode ser o último recurso.

Partilhar no BlueskyPartilhar no FacebookPartilhar no LinkedInPartilhar no TumblrPartilhar em XPartilhar no LinkedInFixar no Pinterest

Mikkel Bang Christensen

Sobre o autor

Mikkel Bang Christensen
Mikkel é o criador e proprietário do miklix.com. Tem mais de 20 anos de experiência como programador informático/desenvolvedor de software profissional e trabalha atualmente a tempo inteiro para uma grande empresa europeia de TI. Quando não está a escrever no blogue, dedica o seu tempo livre a um vasto leque de interesses, passatempos e actividades, que podem, em certa medida, refletir-se na variedade de tópicos abordados neste sítio Web.