OOP no VB: lidando com referências circulares

Visual Basic, VB .NET, ASP, Active X, Access, SQL Server

OOP no VB: lidando com referências circulares

O Visual Basic é construído sobre COM.  COM usa contagem de referências como o seu mecanismo primário para gerenciar o tempo de vida dos objetos. Quando a última referência a um objeto é desfeita, o objeto é destruído.

Um problema conhecido com a contagem de referências é o das referências circulares. Você tem uma referência circular se um objeto A mantém uma referência a um objeto B enquanto B mantém uma referência a A. Atribuindo Nothing a qualquer um dos objetos não será suficiente para destruí-lo até que a referência feita a ele no outro objeto também seja desfeita. Como um objeto detém uma referência para o outro, não basta atribuir Nothing a ambos, porque um não pode ser destruído por estar sendo referenciado pelo outro. É preciso ter o trabalho de destruir primeiro a referência que um dos objetos faz ao outro para quebrar a circularidade. Isto não seria um problema se ocorresse raramente, mas o problema é que uma aplicação VB não fecha enquanto todos os seus objetos não forem destruídos.

Há várias soluções para lidar com referências circulares, mas aquela que me satisfez mais foi um pequeno truque com o uso de eventos.

A técnica separa os dois objetos necessitando de referenciarem-se mutuamente em um pai e um filho. A mecânica dentro do pai é diferente daquela que ocorre no filho.

O pai apenas mantém uma referência ao filho. Nenhum problema se o filho não mantiver também uma referência ao pai. Quando uma referência ao filho é atribuída, o pai cria um objeto especial que funciona como um "canal de eventos" para comunicá-lo com o filho. Ele atribui este objeto a uma propriedade do filho e atribui o objeto a uma variável declarada com a cláusula WithEvents. Sempre que o filho necessita do pai, ele chama um método no objeto "canal de eventos" que dispara um evento. O pai, que está tratando este evento, retorna uma referência a si próprio. É crucial que o filho nunca armazene esta referência retornada (já que isto iria introduzir a referência circular que queremos evitar). A bom da história é que o objeto "canal de eventos" é o mesmo para qualquer relacionamento pai-filho e pode assim ser armazenado em alguma biblioteca ActiveX de uso geral.

Clique aqui para baixar um projeto de exemplo de uso desta técnica.

No projeto de exemplo, temos três classes:

- cMeuPapai é uma classe que funciona como o "canal de eventos" intermediando o relacionamento entre pai e filho.

- cPedido é a classe do objeto pai que contém uma variável nomeada EventosDeFilhos do tipo cMeuPapai declarada com a cláusula WithEvents, o que pemite que cPedido trate os eventos disparados por cMeuPapai.

- cLinhaDePedido é a classe do objeto filho, que possui a propriedade LinkDePai, usada para conter uma referência ao "canal de eventos", aquele mesmo objeto referenciado pela variável EventosDeFilhos dentro do módulo de cPedido. Quando um objeto cLinhaDePedido precisa acessar o seu objeto pai cPedido, usa sua propriedade Pedido que chama o método Papai do objeto cMeuPapai armazenado na propriedade LinkDePai. cMeuPapai, por sua vez, dispara o evento QueroPapai passando por referência uma variável do tipo Object. O evento QueroPapai é processado dentro de cPedido, que retorna uma referência a si próprio na variável Object recebida como argumento do evento. cMeuPapai, então, retorna esta variável como valor de retorno do método Papai chamado por cLinhaDePedido.

Neste exemplo, vê-se que um único objeto cMeuPapai pode ser usado para por o Pedido em comunicação com uma coleção de LinhasDePedido. Quando você quiser criar uma hierarquia onde os objetos de mais alto nível precisam ser comunicados de eventos ocorridos nos objetos de mais baixo nível, a técnica do canal de eventos é uma ótima solução para evitar as referências circulares. Um exemplo seria a necessidade de saber se uma hierarquia sofreu modificação em algum dos seus objetos consultando-se apenas a propriedade "Alterado" do objeto de mais alto nível. Se os objetos de mais baixo nível usarem um objeto canal de eventos para dispararem eventos que sinalizam alterações ocorridas em seus dados, o objeto de mais alto nível pode processar estes eventos e ajustar sua propriedade "Alterado" com base na informação recebida destes eventos. Estes eventos pode ser repassados de um nível para outro da hierarquia até chegar ao topo.