AX Dev Warehouse

Um blog dedicado ao desenvolvimento de soluções em Microsoft Dynamics AX
posts - 41, comments - 18, trackbacks - 1

Práticas e padrões: Pack e Unpack

A serialização de objetos possibilita a persistência e/ou a transmissão de um objeto entre módulos fisicamente separados de uma aplicação. É um processo parecido com transformar o leite em pó e depois conseguir recriar o leite adicionando água.

Diversas linguagens oferecem funcionalidades de serialização, em X++ ela é feita através do padrão pack-unpack. Enquanto em .Net, por exemplo, o objeto pode ser serializado em xml ou outros formatos, no X++ o formato utilizado é o container. Vamos utilizar a classe Person para o nosso exemplo:

 

public class Person
{
    str     name;
    date    birthdate;
}

 

Mas em que consiste “empacotar” um objeto? Basicamente é guardar todas as variáveis deste objeto em um container. O processo inverso, “desempacotar” o objeto, é receber um container contendo os valores das variáveis e carregar as variáveis com estes valores.

Uma versão simplista, mas funcional, do pack-unpack seria assim:

public container pack()
{
    return [name, birthdate];
}

public void unpack(container _packed)
{;
    name        = conpeek(_packed, 1);
    birthdate   = conpeek(_packed, 2);
}

 

Veja a utilização do padrão. Primeiro crio um objeto pessoaA e coloco um nome e data de nascimento neste objeto. Utilizando o pack, guardo este objeto serializado em um container e, em seguida, destruo o objeto. Depois crio um novo Person, pessoaB, e, utilizando o unpack, restabeleço os valores do pessoaA neste novo objeto.

    Person pessoaA;
    Person pessoaB;
    container packed;

    pessoaA = new Person();
    pessoaA.parmName("Joao");
    pessoaA.parmBirthdate(10\10\2000);

    packed = pessoaA.pack();

    pessoaA = null;
    
    pessoaB = new Person();
    
    pessoaB.unpack(packed);
    
    print pessoaB.parmName();
    print pessoaB.parmBirthdate();

    pause;

 

Esta versão funciona, mas poderia ficar um pouco melhor. Preciso repetir cada variável nos dois métodos, pack e unpack, além disso preciso acertar a ordem do container, senão posso colocar o valor de uma variável em outra durante o unpack. Utilizando uma macro, que declaro na classe, consigo resolver estes problemas. Esta nova implementação fica assim:

public class Person
{
    str     name;
    date    birthdate;

    #LOCALMACRO.CurrentList
        name,
        birthdate
    #ENDMACRO
}

public container pack()
{
    return [#CurrentList];
}

public void unpack(container _packed)
{;
    [#CurrentList] = _packed;
}

 

Em CurrentList declaro as variáveis que desejo empacotar para esta classe. Depois o pack-unpack utilizam a lista, eliminando a duplicação de código.

 

Versionamento

Muitas vezes as classes evoluem, ganhando novas variáveis, dadas as necessidades do negócio. Neste caso teremos um problema, imagine que um container contendo um objeto serializado é persistido e depois de algum tempo a aplicação tenta “desempacotá-lo” na classe que agora contém novas variáveis. Não seria possivel, na nossa implementação.

Portanto precisamos de uma pequena mudança. Digamos que a classe Person agora também contém o sexo da pessoa como variável. Então tenho uma variável e um método parm para sexo:

public class Person
{
    str     name;
    date    birthdate;
    Gender  gender;

    #LOCALMACRO.CurrentList
        name,
        birthdate
    #ENDMACRO
}
public Gender parmGender(Gender _gender = gender)
{;
    gender = _gender;
    return gender;
}

 

Agora tenho que mudar a implementação do pack-unpack para que ele saiba diferenciar pacotes contendo um objeto da versão antiga, sem sexo, da nova, com sexo. Adiciono a variável da CurrentList, esta foi a parte fácil. Crio uma constante com a versão corrente, 2.

public class Person
{
    str     name;
    date    birthdate;
    Gender  gender;

    #DEFINE.CurrentVersion(2)
    #LOCALMACRO.CurrentList
        name,
        birthdate,
        gender
    #ENDMACRO
}

 

No pack a versão passa a ser a primeira variável empacotada

public container pack()
{
    return [#CurrentVersion, #CurrentList];
}

 

No unpack, verifico qual a versão do pacote recebido, e implemento um swicth para cada versão que a classe já teve. O método RunBase::getVersion() é um helper que lê o container descobre sua versão.

public void unpack(container _packed)
{
    int version;
    ;
    
    version = RunBase::getVersion(_packed);

    switch (version)
    {
        case #CurrentVersion:
            [version, #CurrentList] = _packed;
            break;

        default:
            [name, birthdate] = _packed;
    }
}

 

Veja a utilização, consigo desempacotar tanto um container contendo somente nome e data de nascimento, quanto a versão mais nova. Lógico que a variável gender assume o valor defaul, Unknown, quando o container não a possui.

 

    Person pessoaA;
    Person pessoaB;
    container packed;
    container oldPacked = ["Joao", 10\10\2000];

    pessoaA = new Person();
    pessoaA.parmName("Joao");
    pessoaA.parmBirthdate(10\10\2000);
    pessoaA.parmGender(Gender::Male);

    packed = pessoaA.pack();

    pessoaA = null;

    pessoaB = new Person();

    pessoaB.unpack(oldPacked);

    print pessoaB.parmName();
    print pessoaB.parmBirthdate();
    print pessoaB.parmGender();

    pessoaB.unpack(packed);

    print pessoaB.parmName();
    print pessoaB.parmBirthdate();
    print pessoaB.parmGender();

    pause;

 

Herança

Uma nova classe, Employee, é criada herdando da classe Person, empregado é uma pessoa, que também possui data de contratação.

class Employee extends Person
{
    date hiringDate;
}
public date parmHiringDate(date _hiringDate = hiringDate)
{;
    hiringDate = _hiringDate;
    return hiringDate;
}

 

Como é Employee filha de Person, posso utilizar os métodos pack-unpack, no entanto, como era de se esperar, a variável hiringDate não será empacotada. Se eu simplesmente sobrescrever o pack-unpack e implementar minha própria versão, estarei duplicando código. Preciso sim, estender os métodos para que o pacote contenha também as variáveis da classe filha.

O pack, portanto, vai pegar o container da classe mãe, e vai empacotar em um novo container, junto com suas próprias variáveis.

public container pack()
{
    container base;

    base = super();

    return [#CurrentVersion, #CurrentList, base];
}

 

Já o unpack vai desempacotar o container em três partes: a versão, sempre a primeira variável, as variáveis da classe filha e um segundo container, contendo as variáveis da classe mãe. Este segundo container é passado para o unpack da classe mãe.

Veja sua utilização, funcionando perfeitamente:

    Employee    empregadoA;
    Employee    empregadoB;
    container   packed;
    ;
    
    empregadoA = new Employee();
    
    empregadoA.parmName("Jose");
    empregadoA.parmBirthdate(1\5\1950);
    empregadoA.parmHiringDate(10\5\1980);
    packed = empregadoA.pack();
    empregadoA = null;
    
    empregadoB = new Employee();
    empregadoB.unpack(packed);

    print empregadoB.parmName();
    print empregadoB.parmBirthdate();
    print empregadoB.parmHiringDate();
    
    pause;

Print | posted on Saturday, February 09, 2008 3:00 PM | Filed Under [ X++ ]

Feedback

No comments posted yet.

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 4 and 4 and type the answer here:

Powered by: