O NeoMatrix Tech está de casa nova!

Você deverá ser redirecionado em 6 segundos. Se não, visite:
http://www.leonelfraga.com/neomatrixtech
e atualize seus favoritos.

Aviso IMPORTANTÍSSIMO!

Aviso aos navegantes:

O NeoMatrix Tech mudou de casa!!!

A partir de agora, acessem pelo novo endereço:

http://www.leonelfraga.com/neomatrixtech

Ué... mas é só o domínio mudou de lugar?

R: Na verdade, não é bem assim hehe. Este domínio que você acessa agora aponta para um blog hospedado no Blogger, enquanto no novo, aponta para um blog na plataforma Wordpress, hospedagem própria, muito mais rápida e com um layout mais agradável de ler ;)

Não vou fechar este domínio igual ao que eu fiz com o NM Light (que já está 100% na nova plataforma). Talvez beeeeeeem depois eu faça isso.

Todos os posts daqui se encontram lá, e novos posts serão colocados somente no novo endereço.
A única coisa que não consegui importar foram os comentários. Mas em breve vai ter um post contando sobre a epopéia que foi migrar o NeoMatrix Tech!

Somente vou fechar a área de comentários daqui. Caso queiram comentar, favor ver o post correspondente no "Novo NeoMatrix Tech" e comentem por lá. É bem melhor! (pena que os permalinks "amigáveis para SEO" não funcionam lá, dá erro 404 e não consigo fazer a configuração funcionar. E olha que eu já vi vários artigos falando desse assunto :( ).

Quem assina o feed, já está lendo o conteúdo do novo NeoMatrix Tech!

domingo, 4 de outubro de 2009

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:

Tabela Niveis

É 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:

  1. Nivel1: O método obtém os dados de um DataReader
  2. Nivel1: Se o DataReader contiver algum registro, continua, senão, cai fora
  3. Nivel1: A propriedade Id é alimentada
  4. Nivel1: A propriedade Descrição é alimentada
  5. Nivel1: Uma nova instância da classe TNivel é inicializada na variável _Antes, e é passado NULL como parâmetro
  6. Nivel1._Antes: O construtor é executado, obtendo os dados de um DataReader
  7. Nivel1._Antes: O Select não retornou registros, então, cai fora
  8. Uma nova instância da classe TNivel é inicializada na variável _Proximo, e é passado “2” como parâmetro.
  9. Nivel1._Proximo: O construtor é executado, obtendo os dados de um DataReader
  10. Nivel1.Proximo: O Select retornou um registro, portanto, vamos alimentar as variáveis
  11. Nivel1.Proximo: A variável Nivel1.Proximo._Id é alimentada
  12. Nivel1.Proximo: A variável Nivel1.Proximo._Descricao é alimentada
  13. Nivel1.Proximo: Uma nova instância da classe TNivel é criada na variável _Antes, e é passado “1” como parâmetro em seu construtor
  14. Nivel1.Proximo.Antes: O construtor é executado, obtendo os dados de um DataReader
  15. Nivel1.Proximo.Antes: O Select retornou um registro (os dados do Nível 1), então, continua.
  16. Nivel1.Proximo.Antes: As variáveis _Id e _Descricao são alimentadas.
  17. Nivel1.Proximo.Antes: Uma nova instância de TNivel é criada na variável _Antes, e é passado NULL como parâmetro
  18. Nivel1.Proximo.Antes.Antes: Obtém os dados de um DataReader, porém o Select não retornou nada.
  19. Nivel1.Proximo.Antes: Uma nova instância de TNivel é criada na variável _Proximo, e é passado “2” como parâmetro em seu construtor
  20. 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

Para tornar este artigo ainda mais interessante, escreva suas críticas (desde que construtivas e sem ofenças), elogios, sugestões, complementos, dúvidas, etc, etc, etc!!!

Sobre o NeoMatrix Tech

Meu blog para assuntos profissionais, ligado com tecnologia.
Dicas de programação (grande parte de C# e ASP.NET, mas não limitado a essa plataforma :-) ), dicas de utilitários, análises de equipamentos e serviços, resenhas sobre sites que eu visito, relacionados com tecnologia, opinião sobre mercado de trabalho, metodologias de desenvolvimento, comportamento no mundo tecnológico...

NeoMatrix Light

  © Blogger templates ProBlogger Template by Ourblogtemplates.com 2008 - Editado e configurado por Leonel F.

Voltar ao TOPO