Momento POG: Armadilha da POO com autorrelacionamentos
Depois de um tempinho sem postagens de programação, finalmente arrumei uma coisnha para poder compartilhar contigo!
Quando você modela uma classe a partir de uma tabela, como você faz com aqueles campos de chave estrangeira, que apontam para uma outra tabela que porventura se torne uma classe também?
Se fomos seguir 100% a POO, devemos fazer um lazy com o objeto em questão, ou seja, criar uma variável privada cujo tipo corresponda a classe daquela tabela, e a sua exposição, se for o caso, em uma propriedade.
Vamos tomar a seguinte tabela como exemplo:
É uma tabela simples, possuindo quatro campos. O detalhe é que os campos ANTES e PROXIMO relacionam-se com o campo ID da mesma tabela, ou seja, um autorrelacionamento (é, com a nova ortografia fica esquisito hehe), ou em jargão menos técnico, uma “tabela que morde o próprio rabo”. E por duas vezes.
Vamos implementar uma classe (em C#, claro, porém, de forma bem simplificada, sem tratamento de erro e tal!) para a tabela em questão:
public class TNivel { #region Variáveis Privadas private int _Id; private string _Descricao; private TNivel _Antes; private TNivel _Proximo; #endregion #region Propriedades public int Id {get {return _Id;} set {_Id = value;}} public string Descricao {get {return _Descricao;} set {_Descricao = value;}} public TNivel Antes {get {return _Antes;} set {_Antes = value;}} public TNivel Proximo {get {return _Proximo;} set {_Proximo = value;}} #endregion #region Métodos public TNiveis(int pNivelId) { if(pNivelId != null) { DataReader dr = getReader("select * from NIVEIS where ID = " + pNivelId.ToString()); if(dr.HasRows) { _Id = (int)dr["ID"]; _Descricao = (string)dr["DESCRICAO"]; _Antes = new TNivel((int)dr["ANTES"]); _Proximo = new TNivel((int)dr["PROXIMO"]); } } } #endregion }
Como você pode ver, os campos PROXIMO e ANTES foram implementados como objetos do tipo TNivel, pois eles são como “ponteiros”, apontando para objetos do mesmo tipo.
O construtor desta classe recebe um ID de nível, e atribui os valores às variáveis privadas, e no caso das variáveis _Antes e _Proximo, ele cria uma nova instância da classe TNivel, passando o respectivo ponteiro em seu construtor.
Vamos inserir alguns dados na tabela?
-------------------------------------- ID DESCRICAO ANTES PROXIMO 1 NIVEL 1 NULL 2 2 NIVEL 2 1 3 3 NIVEL 3 2 NULL --------------------------------------
Montamos os dados nessa tabela de forma que representasse uma lista encadeada, sendo o primeiro nível apontando para o segundo, o segundo apontando para o primeiro e para o terceiro, e o terceiro apontando para o segundo e fechando o encadeamento.
Vamos criar uma instância do nível 1:
TNivel Nivel1 = new TNivel(1);
Quando você executou esse código, notou que o programa ficou parado?
Pois é, o programa fica travado nessa linha! P##ra, o que aconteceu aí, parece que ficou em um looping eterno e não tem nenhum laço!
Vamos fazer agora o bom e velho teste de mesa no método construtor:
- Nivel1: O método obtém os dados de um DataReader
- Nivel1: Se o DataReader contiver algum registro, continua, senão, cai fora
- Nivel1: A propriedade Id é alimentada
- Nivel1: A propriedade Descrição é alimentada
- Nivel1: Uma nova instância da classe TNivel é inicializada na variável _Antes, e é passado NULL como parâmetro
- Nivel1._Antes: O construtor é executado, obtendo os dados de um DataReader
- Nivel1._Antes: O Select não retornou registros, então, cai fora
- Uma nova instância da classe TNivel é inicializada na variável _Proximo, e é passado “2” como parâmetro.
- Nivel1._Proximo: O construtor é executado, obtendo os dados de um DataReader
- Nivel1.Proximo: O Select retornou um registro, portanto, vamos alimentar as variáveis
- Nivel1.Proximo: A variável Nivel1.Proximo._Id é alimentada
- Nivel1.Proximo: A variável Nivel1.Proximo._Descricao é alimentada
- Nivel1.Proximo: Uma nova instância da classe TNivel é criada na variável _Antes, e é passado “1” como parâmetro em seu construtor
- Nivel1.Proximo.Antes: O construtor é executado, obtendo os dados de um DataReader
- Nivel1.Proximo.Antes: O Select retornou um registro (os dados do Nível 1), então, continua.
- Nivel1.Proximo.Antes: As variáveis _Id e _Descricao são alimentadas.
- Nivel1.Proximo.Antes: Uma nova instância de TNivel é criada na variável _Antes, e é passado NULL como parâmetro
- Nivel1.Proximo.Antes.Antes: Obtém os dados de um DataReader, porém o Select não retornou nada.
- Nivel1.Proximo.Antes: Uma nova instância de TNivel é criada na variável _Proximo, e é passado “2” como parâmetro em seu construtor
- Nivel1.Proximo.Antes.Proximo: Bem, aí vc já sabe, basta retornar ao passo 9 :-)
Notaram que conforme ele carrega os objetos _Antes e _Proximo ele vai criando instâncias de TNivel “aninhadas”? Pois é, neste caso que apresentamos, um nível sempre faz referência com pelo menos um outro nível.
Ficamos em uma sinuca de bico… Manter o paradigma POO neste caso não é viável, pois o programa fica criando estas instâncias aninhadas enquanto haver memória para tal. Como saímos disso, sendo que queremos expor os níveis Próximo e Antes como um objeto TNivel?
Graças à instituição POG temos a solução para este caso!!! Vejam o código modificado de forma que as instâncias de TNivel nas propriedades Proximo e Antes sejam criadas conforme elas são acessadas:
public class TNivel { #region Variáveis Privadas private int _Id; private string _Descricao; private int _Antes; private int _Proximo; private TNivel _oAntes; private TNivel _oProximo; #endregion #region Propriedades public int Id {get {return _Id;} set {_Id = value;}} public string Descricao {get {return _Descricao;} set {_Descricao = value;}} public TNivel Antes {get {return new TNivel(_Antes);} set {_oAntes = value;}} public TNivel Proximo {get {return new TNivel(_Proximo);} set {_oProximo = value;}} #endregion #region Métodos public TNiveis(int pNivelId) { if(pNivelId != null) { DataReader dr = getReader("select * from NIVEIS where ID = " + pNivelId.ToString()); if(dr.HasRows) { _Id = (int)dr["ID"]; _Descricao = (string)dr["DESCRICAO"]; _Antes = (int)dr["ANTES"]; _Proximo = (int)dr["PROXIMO"]; } } } #endregion }
Note que agora no construtor de TNivel armazenamos os ID dos níveis Proximo e Antes nas variáveis privadas _Antes e _Proximo, e expomos como propriedade um objeto do tipo TNivel, que em seu método get cria uma nova instância de TNivel com o ID armazenado na variável privada correspondente.
Armazenando os ID’s ao invés de criar uma nova instância diretamente, evita com que ele a aplicação crie as instâncias aninhadas do caso anterior, e somente carregue o nível através das propriedades Proximo e Antes conforme elas são chamadas pela aplicação, sempre armazenando o ID ao invés de criar uma nova instância no construtor.
Então, quando você se deparar com uma situação de autorrelacionamento e quiser manter a POO, use este procedimento, que você não terá problemas!
Um abraço!
0 comentários:
Postar um comentário