Newsgroup: it.comp.lang.delphi
Titolo: Per piacere, ditemele pure

Ho tentato di usare l'object orientation per fare una cosa semplicissima, 
che è quella di disporre in memoria dei dati relativi ad un oggetto, che per 
il caso in esempio, è un titolo finanziario. Ho immaginato due classi che vi 
espongo qui di segutio. Lo so che è una cosa semplicissima, ma sono sicuro 
che avrete molto da dire, o piuttosto da ridire. E' quello che mi aspetto, 
vi prego quindi, così imparo:
 
unit uTitolo;
interface
uses uLoader, Classes, DB, DBTables;
type
  TTitolo = class
    public
      Dati   : TLoader;
      ID      : integer;
      Nome: string;
      constructor Create;
  end;
 
  implementation
 
constructor TTitolo.Create;
begin
  inherited Create;
  Dati := TLoader.Create;
  Dati.SearchID:= 27;
  Dati.DBName:= 'D:\Delphi\Progetti in corso\Bieffe Src\Dati\Dati\';
  Dati.Q:= Dati.LoadData;
  // Imposta i campi dell'oggetto TTitolo con i valori acquisiti 
nell'oggetto Dati
  Id  := Dati.Q.FieldByName('ID').AsInteger;
  Nome:= Dati.Q.FieldByName('nome').AsString;
end;
 
 
unit uLoader;
interface
uses Classes, DB, DBTables;
Type
  TLoader = class
  public
    DBName: string;
    SearchID: integer;
    Q: TQuery;
    function LoadData: TQuery;
  end;
 
implementation
 
function TLoader.LoadData: TQuery;
{ Carica i dati dell'ID indicato, da un dataset su disco }
const err = 'ID %d non trovato';
var strSelect : string;
begin
  result:= nil;
  Q:= TQuery.Create(nil);
  Q.DatabaseName:= DBName;
  Q.SQL.Clear;
  Q.SQL.Add('Select * from Titoli where ID=27');
  Q.Open;
  result:= Q;
end;
end.
 
La classe TLoader crea una TQuery che viene utilizzata dal suo metodo 
LoadData x caricare il record indicato dall'ID e è una funzione perchè così 
passa il risultato della query alla chiamante classe TTitolo che imposta con 
questo i suoi campi. Ma una volta creata la query, dove chiuderla e a chi 
spetta? ........ 
 
 
---
Questa e-mail è stata controllata per individuare virus con Avast antivirus.
http://www.avast.com
 
 

 espongo qui di segutio. [...]
 
In linea di massima l'approccio mi sembra corretto, anche se io non andrei a creare il "Loader" dentro il "Titolo", perché così crei una dipendenza tra due classi che potresti evitare: metti che tu debba generare un titolo senza partire da un caricamento, in questo modo ti dovresti allocare sempre memoria per il "Loader" anche quando non ti serve, oltre al fatto di imbrigliare un pochino l'architettura senza una ragione valida.
 
Piuttosto, farei in modo che il "Loader" restituisca un oggetto "Titolo", inizializzandolo con i dati caricati.
 
Una cosa che hai tralasciato è l'implementazione dei "distruttori": Delphi non usa la Garbage Collection, quindi tutte le Create() vanno a istanziare oggetti che devi distruggere con Free(). Prendendo il tuo esempio, al netto del mio suggerimento sopra, devi fare l'override del metodo Destroy() nella classe *TTitolo* in cui chiami Free() dell'oggetto *TLoader* che hai creato al suo interno. In caso contrario, ti ritroverai una quantità infinita di "memory leak" (
http://it.wikipedia.org/wiki/Memory_leak
).
 
Ciao,
  Marco.
 
-- 
MARCO BREVEGLIERI (Software and Web Developer)
 #Home: 
http://www.marco.breveglieri.name
 
 #Blog: 
http://www.compilaquindiva.com
 
 #DelphiPodcast: 
http://www.delphipodcast.com
 

 dipendenza tra due classi...
 
Marco mi ha anticipato :-)
 
Alle note di Marco aggiungo, anche se non è pertinente con Delphi e OOP, 
che "SELECT * FROM Titoli..." la eviterei: hai bisogno solo di ID e Nome 
quindi meglio utilizzare "SELECT ID, Nome FROM Titoli..."
 
David
 
 

Ho dimenticato: l'altra cosa che farei è esporre ID e Nome della classe TTitolo 
come proprietà e non utilizzando direttamente le variabili pubbliche.
 
Comunque sono convinto che se quel codice lo scriviamo in 10 vengono fuori 
10 versioni diverse della stessa cosa :-)
 
HTH,
David
 
 

Piuttosto, farei in modo che il "Loader" restituisca un oggetto "Titolo", 
inizializzandolo con i dati caricati.
Ciao,  Marco.
-- 
Ciao, grazie davvero per i consigli. Ho provato a seguire il tuo 
suggerimento e ho incluso la creazione dell'oggetto Titolo nel Loader. In 
effetti, può benissimo essere che necessiterò di caricare il Titolo da dati 
immessi dall'utente, piuttosto che da dati già esistenti. Però non so bene 
come fare a referenziare l'oggetto Titolo quando si alternativamente creato 
nell'ambito del Loader o autonomamente.
Per il momento, ho fatto così (ma non mi pare soddisfacente):
type
  TTitolo = class
    public
      ID        : integer;
      Nome      : string;
  end;
  TLoader = class
  public
    unTitolo: TTitolo;
    DBName: string;
    SearchID: integer;
    function  LoadData: TTitolo;
    constructor Create;
  end;
implementation
 
Constructor TLoader.Create;
begin
  inherited create;
  unTitolo:= TTitolo.Create;
end;
 
function TLoader.LoadData: TTitolo;
var Q: TQuery;
begin
  result:= nil;
  Q:= TQuery.Create(nil);
  try
    Q.DatabaseName:= DBName;
    Q.SQL.Clear;
    Q.SQL.Add('Select ID, Nome from Titoli where ID='+inttostr(SearchID));
    Q.Open;
    //unTitolo.Create;   HO PROVATO A CREARLO QUI, MA DA ERRORE !!
    unTitolo.ID:= Q.FieldByName('ID').AsInteger;
    unTitolo.nome:= Q.FieldByName('NOME').AsString;
    result:= unTitolo;
  finally
    Q.Free;
  end;
end;
 
Nel main ho quanto segue:
  .....
  unLoader:= TLoader.Create;
  unLoader.DBName := 'D:\Delphi\Progetti in corso\Bieffe Src\Dati\Dati\';
  unLoader.SearchID := 27;
  unLoader.unTitolo:= unLoader.LoadData;
  showmessage(inttostr(
unLoader.unTitolo.id
));
 
A questo punto avrei un oggetto titolo creato dal loader che è referenziato 
con 'unLoader.unTitolo'.
Se invece volessi un oggetto Titolo che alternativamente potesse contenere 
dati dal loader o dati da utente, come dovrei fare ? 


Ciao Stark,
 
Provo a riepilogare...
 
Abbiamo una classe TTitolo che è definita così:
 
TTitolo = class
private
  FID: Integer;
  FNome: string;
public
  property ID: Integer read FID write FID;
  property Nome: string read FNome write FNome;
end;
 
Quasi identica alla tua, unica differenza è che preferisco esporre proprietà 
e non direttamente variabili.
 
TTitolo è una entità a se stante...
 
Poi abbiamo una classe TLoader che serve per caricare in memoria oggetti 
di tipo TTitolo da una fonte dati. TLoader, che chiamo TTitoloLoader (visto 
che fa il load di Titoli) la definirei così:
 
TTitoloLoader = class
private
  FDBName: string;
public
  function Load(const ATitoloID: Integer): TTitolo;
 
  property DBName: string read FDBName write FDBName;
end;
 
Anche qui vale lo stesso discorso delle proprietà sul DBName. Rispetto alla 
tua ho rimosso le variabili unTitolo e SearchID.
 
SearchID perché lo passo alla funzione Load come parametro, unTitolo perché 
voglio che il mio Loader possa eseguire il caricamento si di un titolo alla 
volta, ma che possa caricarne più di uno durante il suo ciclo di vita.
 
L'implementazione della funzione di Load sarà più o meno così:
 
function TTitoloLoader.Load(const ATitoloID: Integer): TTitolo;
var
  Q: TQuery;
begin
  Q:= TQuery.Create(nil);
  try
    Q.DatabaseName := FDBName;
    Q.SQL.Clear;
 
    // Attenzione: qui sarebbe meglio (molto meglio) utilizzare i parametri...
    Q.SQL.Add('Select ID, Nome from Titoli where ID=' + IntToStr(ATitoloID));
    Q.Open;
    if Q.Bof and Q.Bof then
      raise Exception.CreateFmt('Impossibile trovare il titolo %d.', [ATitoloID]);
 
    result := TTitolo.Create;
 
    // Hai costruito la query con le colonne, se ci vai per posizione è più 
performante :-)
    result.ID:= Q.Fields[0].AsInteger;
    result.nome:= Q.Fields[1].AsString;
  finally
    Q.Free;
  end;
end;
 
Ci siamo fino a qui?
 
Adesso dichiaro una nuova classe TListaTitoli in questo modo:
 
TListaTitoli = class
private
  FLoader: TTitoloLoader;
  FLista: TObjectList<TTitolo>;
public
  constructor Create;
  destructor Destroy; override;
 
  function AddNew: TTitolo;
  function Select(const ATitoloID: Integer): TTitolo; 
 
  ...
end;
 
Le implementazioni:
 
constructor TListaTitoli.Create;
begin
  inherited Create;
  FLoader := TTitoloLoader.Create;
  FLoader.DBName := 'D:\Delphi\Progetti in corso\Bieffe Src\Dati\Dati\';
  FLista := TObjectList<TTitolo>.Create(True);
end;
 
destructor TListaTitoli.Destroy;
begin
  FLista.Free;
  FLoader.Free;
  inherited Destroy;
end;
 
function TListaTitoli.AddNew: TTitolo;
begin
  result := TTitolo.Create;
  FLista.Add(result);
end;
 
function TListaTitoli.Select(const ATitoloID: Integer): TTitolo; 
begin
  result := FLoader.Load(ATitoloID);
  FLista.Add(result);
end;
 
Nel tuo Main avrai:
 
...
private
  FListaTitoli: TListaTitoli;
...
 
constructor ...
begin
  inherited ...;
  FListaTitoli := TListaTitoli.Create;
end;
 
destructor ...
begin
  FListaTitoli.Free;
  inherited ...;
end;
 
procedure ...
var
  Titolo27, NuovoTitolo: TTitolo;
begin
  Titolo27 := FListaTitoli.Select(27);
  ShowMessage(IntToStr(Titolo27.ID)); //27
 
  NuovoTitolo := FListaTitoli.AddNew;
  ShowMessage(IntToStr(NuovoTitolo.ID)); //0
end;
 
Spero di esserti stato di aiuto.
 
David
 
 
 

Ciao Stark,
 
Ti allego un file di testo con lo stesso codice della mia risposta precedente ma formattato in modo più umano.
 
Ciao
David

     result.nome:= Q.Fields[1].AsString;
 
Non condivido questa cosa.
Pregiudicare la leggibilità per poi dover ricordare di cambuiare gli indici delle colonne se cambia la query per guadagnare quanto, 2 millisecondi?
E sopratutto, tu fai cosi' o lo stai solo suggenrendo?
 
Infine, meglio precisare che questo approccio da per scontato che la free di un istanza di TTitolo sia fatta dal *chiamante*.
 
A.

"David Lastrucci"  ha scritto nel messaggio 
news:c547f3746d4c8...@news.eternal-september.org...

....
Non so come ringraziarti, anzi come ringraziarvi! Ho letto avidamente e 
velocemente. Adesso rileggo con maggiore attenzione e provo ad implementare. 
Poi vi dirò..
PS: Finalmente un esempio pratico (perchè la teoria l'ho studiata, ma quando 
passo alla pratica non so cosa fare..). Grazie
Stark 


 free di un istanza di TTitolo sia fatta dal *chiamante*.
 
Vero, non lo avrei creato li TTitolo, ma lo avrei passato ad una funzione 
che doveva solo occuparsi di mappare le proprietà con i dati del DB... Ma 
avevo paura di mettere troppa carne al fuoco.
 
Saluti,
David
 
 

Ti allego un file di testo con lo stesso codice della mia risposta 
precedente ma formattato in modo piů umano.
Ciao David
....
Allora, ho provato ad implementare e a capire. Prima di tutto, aiutami a 
risolvere un problema di esecuzione. Nel Main ho semplificato come segue:
var
  Titolo27: TTitolo;
begin
  FListaTitoli := TListaTitoli.Create;
  Titolo27 := FListaTitoli.Select(27);
  ..... := FListaTitoli.AddNew;
 
Nel create di FListaTitoli, va in Access Violation nella seguente 
istruzione:
  FLista := TObjectList(TTitolo).Create(True);
probabilmente perchč TTitolo ancora non esiste, essendo creato nel metodo 
Load. E' cosě?
Se si, quale potrebbe essere la soluzione ? Poi ti dirň di alcuni altri 
dubbi..
- 
 
 
---
Questa e-mail č stata controllata per individuare virus con Avast antivirus.
http://www.avast.com
 
 

   FLista := TObjectList(TTitolo).Create(True);
 
TTitolo non è un oggetto, ma il nome di una classe.
Stai "castando" (cioè trattando come un oggetto di diverso tipo) una classe (TTitolo) e non un oggetto (istanza di TTitolo).
 
Se quello che stai cercando di fare è specializzare la lista di oggetti sul tipo "TTitolo", allora devi approfondire l'argomento *Generics*:
http://docwiki.embarcadero.com/RADStudio/XE8/en/Overview_of_Generics
 
 
C'è un po' di confusione... e "l'inverno sta arrivando", Stark. :D


"Marco Breveglieri"  ha scritto nel messaggio 
news:9f839572-b450-4bf7...@googlegroups.com...
C'è un po' di confusione... e "l'inverno sta arrivando", Stark. :D
-- 
Se non avessi confusione, non interpellerei il forum con domande stupide..
Comunque avevo frettolosamente implementato il codice suggeritomi da David 
(vedi messaggio precedente)...  Ora cercherò di capirlo meglio e tenterò di 
rimediare da me. Grazie comunque..
 
 
---
Questa e-mail è stata controllata per individuare virus con Avast antivirus.
http://www.avast.com
 
 

Ciao Stark,
 
nel mio codice c'è
 
TObjectList<TTitolo>.Create(True);
 
non 
 
TObjectList(TTitolo).Create(True);
 
Sono due sintassi completamente diverse... Con che versione di Delphi stai 
lavorando?
 
David
 
 

 Se non avessi confusione, non interpellerei il forum con domande stupide..
 
Hai ragione, però se inizi a scrivere codice allora devi già sapere il significato di quello che digiti.
 
Se hai dubbi, puoi sempre chiedere prima.
 
Ciao!
  Marco.


"David Lastrucci"  ha scritto nel messaggio 
news:c547f37471928...@news.eternal-september.org...

...
Con Delphi Win32 2007. Ovviamente l'avevo notato, ma avevo creduto in un 
errore di battitura.... 


 errore di battitura....
 
Non è un errore di battitura :-)
 
Comunque con Delphi 2007 i Generici non li hai... Converti tutto quello che 
è TObjectList<TTitolo> con un semplice TObjectList che in D2007 hai a disposizione.
 
HTH
David
 
 

Comunque con Delphi 2007 i Generici non li hai... Converti tutto quello che
è TObjectList<TTitolo> con un semplice TObjectList che in D2007 hai a 
disposizione.
David
....
Fatto e funziona tutto. Però non capisco appieno il vantaggio dell'uso di 
una objectList, salvo che non presuma di poter creare enne istanze di 
TTitolo. Mi puoi illuminare ? 
