Referência circular. Existe solução sem type casts?
05/03/2006
0
Peço que, por favor, me esclareçam uma dúvida...
Situação:
[list:f058a83c93][*:f058a83c93][b:f058a83c93]TClasseBase[/b:f058a83c93] = class(TObject), [b:f058a83c93]abstrata[/b:f058a83c93]. Unit ´untClasseBase´.
[*:f058a83c93][b:f058a83c93]TClasseA[/b:f058a83c93] = class(TClasseBase), implementando as propriedades e métodos necessários de acordo com com necessidade X. Unit ´untClasseA´, contendo na uses da interface, ´untClasseBase´.
[*:f058a83c93][b:f058a83c93]TClasseB[/b:f058a83c93] = class(TClasseBase), implementando as propriedades e métodos necessários de acordo com com necessidade Y. Unit ´untClasseB´, contendo na uses da interface, ´untClasseBase´.
[*:f058a83c93][b:f058a83c93]TMinhaClasse[/b:f058a83c93] = class(TObject) que contém uma propriedade ´Classe´ do tipo TClasseBase. Esta propriedade ´Classe´ é inicializada pelo constrututor de forma a ser TClasseA ou TClasseB. Qual das duas classes ela será, dependerá do parametro passado ao construtor. Unit ´untMinhaClasse´, contendo na uses da interface as units ´untClasseBase´, ´untClasseA´ e ´untClasseB´
TQualClasse = (qcClasseA, qcClasseB); TMinhaClasse = class(TObject) private FClasse: TClasseBase; public {...} property Classe read FClasse write FClasse; end; implementation constructor TMinhaClasse.Create(AQualClasse: TQualClasse); begin {...} case AQualClasse of qcClasseA: Classe := TClasseA.Create; qcClasseB: Classe := TClasseB.Create; end; {...} end;
Eu gostaria de manter na mesma unit TClasseBase e TMinhaClasse, como ´untClasseGeral´, porém ela necessitaria ter em sua uses da interface, ´untClasseA´ e ´untClasseB´. Por sua vez, ´untClasseA´ e ´untClasseB´ necessitam em sua uses da interface de ´untClasseGeral´. Daí vem o problema de referencia circular.
Fiz alguns testes básicos e talvez seja possível implementar isso utilizando type casts, mas é a melhor (ou única) solução? Ou seria esta a forma correta de trabalhar esta questão?
Não é um problema crítico, mas gostaria, se possível, de organizar melhor o código, com menos units.
Desde já agradeço a colaboração.
Vinicius2k
Posts
06/03/2006
Michael
Como [b:b4f7fc4d08]TClasseBase [/b:b4f7fc4d08]é abstrata e [b:b4f7fc4d08]TClasseA [/b:b4f7fc4d08]e [b:b4f7fc4d08]TClasseB [/b:b4f7fc4d08]são implementações dela, vc pode fazer isso:
type TClasseBaseClass = class of TClasseBase; TClasseBase = class (...) constructor TMinhaClasse.Create(_Class: TClasseBaseClass); begin {...} FClasse = _Class.Create; end;
Desta forma vc não precisa incluir a unit onde [b:b4f7fc4d08]TClasseA [/b:b4f7fc4d08]e [b:b4f7fc4d08]TClasseB [/b:b4f7fc4d08]estão declaradas, eliminando o erro de referência circular. Pessoalmente neste contexto prefiro esta abordagem do que fazer uso de parâmetros para saber qual classe instanciar. E fica bem mais limpo o código.
Porém, isso não vai funcionar se estas classes, além de implementarem os métodos e propriedades de [b:b4f7fc4d08]TClasseBase[/b:b4f7fc4d08], publicarem algum atributo específico, pois para ter acesso às características de cada classe vc precisaria declarar as units delas.
Vc então usa este esquema assim:
uses untClasseGeral, untClasseA, untClasseB; var ClasseA, ClasseB: TMinhaClasse; begin ClasseA := TMinhaClasse.Create(TClasseA); ClasseB := TMinhaClasse.Create(TClasseB); end;
E viva a orientação a objetos! ;-)
[]´s
06/03/2006
Vinicius2k
Muito obrigado pela resposta!
Veja bem, apesar das classes que implementam a classe abstrata não publicarem nada de novo, eu acho que não vou conseguir utilizar a sua abordagem.
TMinhaClasse será instanciada pela aplicação e a sua propriedade Classe será diretamente manipulada. Exemplo:
var MinhaClasse: TMinhaClasse; {...} case opcao of 1: MinhaClasse := TMinhaClasse.Create(qcClasseA); 2: MinhaClasse := TMinhaClasse.Create(qcClasseB); end; MinhaClasse.Classe.FacaIsto; MinhaClasse.Classe.FacaAquilo;
As implementações A e B de TClasseBase são bem distintas e a aplicação irá decidir qual das duas classes será utilizada em Run Time. Por este motivo ainda vejo a necessidade de que o construtor conheça a classe que irá instanciar para a propriedade Classe.
Estou correto com meu raciocício?
T+
06/03/2006
Michael
if FClasse is TClasseA then FClasse.FacaIsso else if FClasse is TClasseB then FClasse.FacaAquilo;
A minha sugestão vai funcionar, com certeza. Porém, pelo exemplo que vc passou, parece que vc vai fazer como eu esperava: os mesmos métodos serão chamados, independentemente de quem seja [b:9148db7695]FClasse[/b:9148db7695]. Correto?
A vantagem de usar um parâmetro do tipo [b:9148db7695]class of[/b:9148db7695] ao invés de um tipo enumerado é a abstração que se ganha. [b:9148db7695]TMinhaClasse [/b:9148db7695]vai funcionar não importando quem seja [b:9148db7695]FClasse[/b:9148db7695]. Se no futuro vc criar [b:9148db7695]TClasseC[/b:9148db7695], não vai precisar mudar nada em [b:9148db7695]TMinhaClasse[/b:9148db7695]. Será apenas necessário chamar o construtor passando a classe correta.
Classe := TMinhaClasse.Create(TClasseC);
Mas se eu entendi mal e no seu caso [b:9148db7695]TMinhaClasse [/b:9148db7695]realmente precisa saber quem é [b:9148db7695]FClasse[/b:9148db7695], há pelo menos duas soluções. Uma já foi apresentada, e repito aqui:
if FClasse is TClasseA then FClasse.FacaIsso else if FClasse is TClasseB then FClasse.FacaAquilo;
Entretanto para que [b:9148db7695]TClasseA [/b:9148db7695]e [b:9148db7695]TClasseB [/b:9148db7695]sejam reconhecidas vc vai precisar declarar suas units na cláusula [b:9148db7695]uses[/b:9148db7695], e vamos ter novamente o problema de referência circular.
A outra solução - que realmente resolve o problema - é usar os atributos de [b:9148db7695]TObject[/b:9148db7695], mais precisamente a propriedade [b:9148db7695]ClassName [/b:9148db7695]ou o método [b:9148db7695]ClassNameIs[/b:9148db7695]. Ambos servem para se obter a representação string do nome de uma classe. O exemplo anterior poderia ser mudado para:
if FClasse.ClassNameIs(´TClasseA´) then FClasse.FacaIsso else if FClasse.ClassNameIs(´TClasseB´) then FClasse.FacaAquilo;
Isso elimina a necessidade de se referenciar as units.
Se o seu caso for realmente esse, parece que o acoplamento entre [b:9148db7695]TMinhaClasse [/b:9148db7695]e [b:9148db7695]TClasseA [/b:9148db7695]e [b:9148db7695]TClasseB [/b:9148db7695]é alto o suficiente para justificar a permanência de todas as classes na mesma unit, o que anularia o problema que motivou vc a postar essa mensagem. Creio porém que não é este o seu caso. Ou é? ;-)
[]´s
06/03/2006
Vinicius2k
Exatamente. Os mesmos métodos serão executados, independente de qual classe esteja instanciada em FClasse de TMinhaClasse.
O caso é que as classes ´filhas´ (A, B... N) sempre terão a sua própria implementação dos métodos e Gets/Sets da classe abstrata.
Classe := TMinhaClasse.Create(TClasseC);
Não... acho que fui eu quem não entendi a sua proposta da primeira vez... :oops: Não percebi na sua sugestão o [b:a511771aeb]class of[/b:a511771aeb], desta forma eu penso que poderia agrupar as classes na mesma unit.
Porém, ainda desta forma, eu também precisaria ter todas as units das classes filhas (A, B... N) na uses da unit aonde eu for criar TMinhaClasse e efetuar aquele case de acordo com a opção do usuário, certo?
No aspecto organização, não ficaria melhor então deixar como está (units independentes para TClasseBase e TMinhaClasse e o parametro do tipo enumerado)? Isto porque, neste caso eu só preciso ter na uses de onde estou criando TMinhaClasse a unit aonde se encontra TMinhaClasse.
Afe... meio confuso isso... :roll:
06/03/2006
Michael
Não. Acho que vc não pegou o ´espírito da coisa´... ;-)
Veja com um exemplo:
unit [b:48e3b0fc6d]untClasseGeral[/b:48e3b0fc6d]
type TClasseBaseClass = class of TClasseBase; TClasseBase = class public constructor Create; virtual; abstract; end; TMinhaClasse = class private FClasse: TClasseBase; public constructor Create(_Class: TClasseBaseClass); end; implementation { TMinhaClasse } constructor TMinhaClasse.Create(_Class: TClasseBaseClass); begin FClasse := _Class.Create; end;
unit [b:48e3b0fc6d]untClasseAB [/b:48e3b0fc6d](coloquei na mesma unit para economizar espaço aqui)
interface uses Unit2, Dialogs; type TClasseA = class(TClasseBase) public constructor Create; override; end; TClasseB = class(TClasseBase) public constructor Create; override; end; implementation { TClasseA } constructor TClasseA.Create; begin inherited; ShowMessage(´Construtor da classe TClasseA´); end; { TClasseB } constructor TClasseB.Create; begin inherited; ShowMessage(´Construtor da classe TClasseB´); end;
unit [b:48e3b0fc6d]Teste[/b:48e3b0fc6d]
uses untClasseGeral, untClasseAB; (...) var MinhaClasse1, MinhaClasse2: TMinhaClasse; begin MinhaClasse1 := TMinhaClasse.Create(TClasseA); MinhaClasse2 := TMinhaClasse.Create(TClasseB); end;
Desta forma abstrai-se [b:48e3b0fc6d]TMinhaClasse [/b:48e3b0fc6d]da existência de [b:48e3b0fc6d]TClasseA [/b:48e3b0fc6d]e [b:48e3b0fc6d]TClasseB[/b:48e3b0fc6d], ou de qualquer outra derivada de [b:48e3b0fc6d]TClasseBase[/b:48e3b0fc6d]. Vc não precisa usar o case com o tipo enumerado pq o construtor de TMinhaClasse vai receber a classe certa que ele tem que inicializar. Vc não vai precisar declarar as units de TClasseA e TClasseB na unit de TMinhaClasse, pois ela não tem conhecimento delas.
Pessoalmente acho que não, pelas razões já apresentadas. Para que fazer um [b:48e3b0fc6d]case [/b:48e3b0fc6d]para diferenciar o parâmetro se vc já sabe que ele vem correto, pronto para vc usar? ;-)
Pegou agora? ;-)
Se ainda estiver com dúvidas avise aqui q mando um exemplo para vc.
[]´s
06/03/2006
Vinicius2k
No exemplo que você passou:
uses untClasseGeral, untClasseAB; (...) var MinhaClasse1, MinhaClasse2: TMinhaClasse; begin MinhaClasse1 := TMinhaClasse.Create(TClasseA); MinhaClasse2 := TMinhaClasse.Create(TClasseB); end;
Note que na unit [b:bb7890224a]Teste[/b:bb7890224a] você precisou declarar a unit [b:bb7890224a]untClasseAB[/b:bb7890224a], porque precisa passar a classe que deseja criar como parametro para o construtor de TMinhaClasse.
O [b:bb7890224a]case[/b:bb7890224a] ao qual me referi e na unit aonde TMinhaClasse é criada, no caso, a unit [b:bb7890224a]Teste[/b:bb7890224a]. Qual classe será criada dependerá de uma escolha do usuário e neste caso, eu precisaria preparar para qualquer escolha, colocando na uses todas as classes filhas de TClasseBase. Então, meu código ficaria assim:
uses untClasseGeral, untClasseA, untClasseB,..., untClasseN; (...) var MinhaClasse: TMinhaClasse; begin case opcao of 1: MinhaClasse := TMinhaClasse.Create(TClasseA); 2: MinhaClasse := TMinhaClasse.Create(TClasseB); {...} N: MinhaClasse := TMinhaClasse.Create(TClasseN); end; end;
Entende porque neste caso, eu fico com a impressão de que seria preferível as ´uses´ na TMinhaClasse? Para que não seja necessário para a unit [b:bb7890224a]Teste[/b:bb7890224a], conhecer todas a implementações de TClasseBase.
O que está ´pegando´ é: Quem [b:bb7890224a]deve conhecer[/b:bb7890224a] todas as implementações? A unit de [b:bb7890224a]TMinhaClasse[/b:bb7890224a] ou a unit [b:bb7890224a]Teste[/b:bb7890224a]
Só para melhorar o contexto, desde o princípio eu falei sempre em A e B, mas na verdade eu tenho previsão de possuir entre 15 e 20 implementações diferentes para TClasseBase.
06/03/2006
Vinicius2k
Só para registrar, implementei conforme sua primeira sugestão e funcionou perfeitamente. A única questão que persiste é:
É o mais correto (segundo as ´boas maneiras´ da OOP) que a unit do projeto que instancia TMinhaClasse tenha em sua ´uses´ todas as units que implementam TClasseBase (todas apenas porque eu preciso de todas as possibilidades no meu case)?
Analisando mais calmamente, penso que sim. Pois se eu implementar uma nova classe descendente de TClasseBase, precisaria modificar apenas a unit do projeto...
Grato pela ajuda ! ;)
07/03/2006
Michael
Desta forma o seu código inicial ainda pode funcionar. A não ser que vc precisa declarar untClasseA..untClasseN na seção [b:c2f6e5aa16]interface[/b:c2f6e5aa16]. Mas pelos posts isso não ficou claro.
Sobre as units, se as implementações de TClasseBase forem só suas, então não vejo problema em tê-las na mesma unit untGeral. Inicialmente eu pensei que vc criaria as classes gerais e posteriormente outro programador poderia, ou vc mesmo no futuro, implementar TClasseA..TClasseN. Desta forma acho que não seria indicado o acesso à unit untGeral.
De qualquer jeito não conheço nenhuma regra da OO que fale sobre isso. Na verdade isso parece mais composição de classes, de UML.
[]´s
07/03/2006
Vinicius2k
Não sei se você ainda não compreendeu esta parte, mas a unit ´Geral´ é apenas para a TClasseBase e TMinhaClasse. As implementações ´filhas´ de TClasseBase ficarão uma em cada unit.
O problema da referência circular vinha de que (imaginando units distintas):
1. A TClasseBase está em uma unit sozinha, apenas com as assinaturas dos métodos e propriedades.
2. Cada implementação da TClasseBase chamada de TClasseA, TClasseB...TClasseN, cada uma delas estando em units distintas e, logicamente, por serem derivadas da classe abstrata TClasseBase precisam ter em sua uses a unit de TClasseBase (1).
3. A unit de TMinhaClasse precisa ter em sua uses da interface a unit de TClasseBase (1). Isto porque existe esta classe possui uma propriedade chamada ´Classe´ do tipo TClasseBase.
Como meu construtor antigo de TMinhaClasse precisava saber qual classe ´filha´ instanciar para a prorpiedade ´Classe´ eu também precisava de das units de cada classe ´filha´ de TClasseBase em sua uses (2).
Até aqui, sem problemas: ´2´ referenciava ´1´ e ´3´ referenciava ´1´ e ´2´. A referência circular ocorria quando eu pretendia agregar na mesma unit (a untGeral) os ítens 1 e 3. Então, ´2´ refenciava ´1´ e ´1´ referenciava ´2´ = erro.
Eu consegui fazer a agregação usando ´class of´ no construtor de TMinhaClasse e retirando da uses da interface a lista de units das classes ´filhas´ de TClasseBase porque o meu construtor não precisa mais saber qual classe ´filha´ instanciar, ele irá recebê-la como parametro.
No form aonde TMinhaClasse é instanciada, eu tenho na uses da interface a unit de TMinhaClasse, porque um objeto chamado ´MinhaClasse´, do tipo TMinhaClasse é pertencente ao form. Na uses da implementation deste form estão as units das classes ´filhas´ de TClasseBase porque no ´Create´ do form é lida a opção do usuário e efetuado o [b:a8e50db3ab]case[/b:a8e50db3ab] para instanciar ´MinhaClasse´.
case opcao of // este código está no form que instancia TMinhaClasse 1: MinhaClasse: TMinhaClasse.Create(TClasseA); 2: MinhaClasse: TMinhaClasse.Create(TClasseB); {... etc ...} end;
Não falei que era confuso! É difícil explicar isso...
Mas o importante é que está como eu gostaria agora ;)
08/03/2006
Michael
E a parada da DevMedia? Mandou o email?
[]´s
Clique aqui para fazer login e interagir na Comunidade :)