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;