Newsgroup: it.comp.lang.delphi
Titolo: TObjectList vs TClassList

In un mio programma ho una classe, che per esempio si chiama TAccount.
Al momento, ipotizzando di dover gestire più account, le gestisco
raccogliendole in una TObjectList e funziona tutto senza problemi, però per
una questione 'stilistica' vorrei implementare una soluzione più pulita.
Infatti, quando devo accedere ad un elemento della lista devo 'farcire' la
procedura di cast:
  var Acc: TAccount;
  Acc := TAccount(TMiaLista[0]);
Quindi pensavo di fare una classe TAccounts con lo scopo di poter poi
scrivere del codice più pulito senza dover mettere cast dappertutto.
Pensavo di creare TAccounts derivandolo da TObjectList ma poi, guardando il
codice per capire se era la soluzione giusta, mi sono imbattutto nella
classe TClassList che sembrerebbe ancora più azzeccata.
Quindi cosa dovrei fare, derivare da TClassList? Cosa mi consigliate?
-- 
Lis
Mercatino: 
http://snurl.com/sito_lis
Aiuta la ricerca contro il cancro: 
http://snurl.com/ida_team

 'farcire' la procedura di cast:
in pratica, hai bisogno di una lista type-safe (che accetti solo elementi
di un dato tipo e restituisca solo elementi di un dato tipo). Ci sono due
modi di implementare ciň: derivare da TList o fare un wrapper di TList (ce
ne sarebbe un terzo molto comodo di derivare da TCollection e
TCollectionItem). 
Io uso il sistema wrapper, come questa:
unit FormList;
interface
uses
  Classes, Forms;
type
  TFormList = class
  private
    FList: TList;
    function GetItems(I: Integer): TCustomForm;
    procedure SetItems(I: Integer; Value: TCustomForm);
  protected
  public
    constructor Create;
    destructor Destroy; override;
    function Add(Form: TCustomForm): Integer;
    function IndexOf(Form: TCustomForm): Integer;
    procedure Delete(Form: TCustomForm);
    procedure Clear;
    function Count: Integer;
    property Items[I: Integer]: TCustomForm read GetItems write SetItems;
    default; 
  end;
 // PCustomForm = ^TCustomForm;
implementation
uses
  SysUtils, Windows;
{ TFormList }
function TFormList.Add(Form: TCustomForm): Integer;
//var   i: Integer;
begin
//  i:= Integer(PCustomForm(Form));
//  OutputDebugString(PChar(IntToStr(i)));
  Result:= FList.Add(Form)
end;
procedure TFormList.Clear;
begin
  FList.Clear;
end;
function TFormList.Count: Integer;
begin
  Result:= FList.Count
end;
constructor TFormList.Create;
begin
  FList:= TList.Create;
end;
procedure TFormList.Delete(Form: TCustomForm);
var
  i: Integer;
begin
  i:= IndexOf(Form);
  if i > -1 then
    FList.Delete(i);
end;
destructor TFormList.Destroy;
begin
  FreeAndNil(FList);
  inherited;
end;
function TFormList.GetItems(I: Integer): TCustomForm;
begin
  Result:= TCustomForm(FList[I])
end;
function TFormList.IndexOf(Form: TCustomForm): Integer;
begin
  Result:= FList.IndexOf(Form)
end;
procedure TFormList.SetItems(I: Integer; Value: TCustomForm);
begin
  FList[I]:= Value
end;
end.
-- 
Mauro Falzari 
   [ 
http://digilander.libero.it/mauro.falzari
 ]
 
 "Se guardi nell'abisso, anche l'abisso guarda in te." 
   (Friedrich Nietzsche)

 
E' stata la prima idea che mi č venuta in mente ma poi ho iniziato a
guardare TObjectList, TClassList (e tutte quelle contenute nella stessa
unit) e sono andato in crisi ;-)
grazie


   Acc := TAccount(TMiaLista[0]);
Se il problema e' solo quello:
TAccountList = class(TObjectList)
protected
  // GetAccount, SetAccount
public
  function Add(anAccount: TAccount): integer;
  // altri metodi a tuo piacimento
  property Accounts[Index: integer]: TAccount read GetAccount write
SetAccount;
end;
Cosi' il cast lo fai dentro i metodi della lista.
La TClassList ha altre utilita'.
i me 2 ghei
Saluti
Paolo Arosio
-- 
"La dimostrazione dell'esistenza di specie intelligenti nell'universo
 e' che non ci vengono a trovare" [Calvin & Hobbes]

 La TClassList ha altre utilita'.
Cioè? Ho guardato l'help ma non dice praticamente nulla (TClassList
maintains a list of classes). Hai qualche esempio pratico in cui può essere
utile?
grazie


 contro di ogni soluzione? 
definiamo
- TInhList: la soluzione di Paolo, cioè la lista ereditata da TObjectList;
- TWrapList: il wrapper di TList;
- procedure Any(List: TList), come una procedura qualsiasi che prende come 
parametro un oggetto TList.
Il vantaggio di TInhList è che è compatibile con tutti i metodi che accettano 
una TList come parametro, però i metodi di TList e TObjectList restano ancora 
utilizzabili, quindi uno potrebbe usarli per aggiungere alla lista oggetti 
diversi da quelli previsti.
Il vantaggio di TWrapList è che è più type-safe, però non è compatibile con i 
metodi che manipolano TList. 
TInhList richiede la scrittura di meno codice rispetto a TWrapList.


 compatibile con i metodi che manipolano TList. 
E' vero, però si potrebbe rendere compatibile con i metodi di TList
semplicemente riscrivendoli nella nuova classe wrapper.
Antonio

 semplicemente riscrivendoli nella nuova classe wrapper.
temo che sia un problema di incompatibilità dei tipi... al compilatore non 
andrebbe giù il tentativo di passare un oggetto ereditato da TObject (la 
wrapper list) dove si aspetterebbe un TList o derivato


 essere utile?
Serve a mantenere riferimenti a classi, non ad istanze, quindi puo'
essere utile per una factory; non ho comunque un esempio pronto.


 o derivato
Sì, ho frainteso ciò che intendevi. Pensavo ad una incompatibilità dei
metodi non dei tipi. Nel mio caso bastava riscrivere praticamente gli
stessi metodi della TList nella classe wrapper. Sorry ;-)
Antonio

 wrapper list) dove si aspetterebbe un TList o derivato
Esatto, mi sono scontrato con questo problema per cercare di implementare
l'Assign (ossia per "copiare" gli oggetti da una Lista ad un'altra)


 essere utile per una factory; non ho comunque un esempio pronto.
grazie


 "copiare" una lista in un altra. Con la TObjectList userei l'Assign
Assign e' disponibile da TPersistent. Per le liste direi che si dovrebbe
scrivere un metodo Clone, tipo:
procedure TSomeList.Clone(dest: TSomeList);
begin
  // verifica compatibilita' classe degli elementi
  dest.Clear;
  dest.Capacity := Capacity;
  for each item in self
    dest.add(item)
end;
Sicuro che ti serva?


 da TObjectList e dovrebbe essere piů semplice, no?
Sto riprovando ad implementare l'assign ma non ci riesco.
Innanzi tutto vi spiego perchč mi serve, cosě magari avete anche qualche
alternativa.
La classe si chiama TAccountList e risiede in una form. Per poter
configurare gli account, passo questa lista come parametro ad una form che
perň deve farsi una copia di tutti i dati perchč poi devo aggiornarli solo
se l'utente conferma le modifiche apportate.
Quindi pensavo ad una cosa del genere:
  AccountsForm := TFrmAccounts.Create(Self, FAccountList);
  try
    if AccountsForm.ShowModal = mrOk then begin
      //se l'utente preme ok io cancello la lista "principale" e copio
      //i dati da quella contenuta nel form di editing
      FAccountList.Clear;
      FAccountList.Assign(AccountsForm.Accounts);
      UpdateAccounts;
    end;
  finally
    FrmAccounts.Free;
  end;
Questo invece č come ho cercato di implementare l'assign (ho lasciato solo
il codice interessato):
  TAccountList = class
  private
    FAccountList: TObjectList;
  public
    property AccountList: TObjectList read FAccountList;
    procedure Assign(AccountList: TAccountList);
  end;
  procedure TAccountList.Assign(AccountList: TAccountList);
  begin
    FAccountList.Assign(AccountList.FAccountList);
  end;
In teoria quindi richiamo il metodo Assign di FAccotunList (che č un
TObject) e gli passo come parametro una TObjectList
(AccountList.FAccountList). Vi sembra corretto tutto il giro?
Teoricamente mi sembra giusto, in pratica perň no, dopo aver eseguito
l'assign trovo che AccountList contiene dati 'sporchi'


 end;
Tra classi e puntatori mi sa che ho un bel rebelòt in testa :-)
Comunque, quel dest.add, aggiunge solo un riferimento o ne fa una copia?
In un post che ho appena scritto ho spiegato perchè mi serve la copia


 metodi che manipolano TList. 
Imho, io la preferisco.
Se suo questa tecnica preferisco che la classe esponga solo i metodi che 
io ritengo opportuni, obbligando (e obbligandomi....) ad usare solo 
quelli.  Inoltre, un giorno potrei decidere di usare un altra classe al 
posto di TList e questo potrebbe impattare sulla interfaccia esposta 
dalla mia classe.
Bye
A

   end;
A parte che chiami la Free di FrmAcc e hai creato AccountForms.
Hai la lista che mantiene riferimenti a degli oggetti (che hai
istanziato altrove). Passi la lista alla form in modo che questa li
rappresenti in qualche modo per l'utente su dei controlli visuali.
L'utente modifica *il contenuto dei controlli visuali*: puoi decidere se
deve confermare le modifiche ad un account prima di passare ad un altro
oppure se bufferizzare tutte le modifiche e applicarle o rifiutarle
tutte insieme alla fine, con il click su OK o su Cancel.
Nel primo caso (richiesta conferma modifica per ciascun record: pulsante
Apply o simile) puoi evitare di avere una copia: quando l'utente Applica
le modifiche prendi il contenuto dei controlli visuali e lo metti nei
campi dell'oggetto corrente.
Nel secondo caso hai piu' alternative: quando l'utente modifica un
controllo e poi passa ad un altro oggetto puoi duplicare il singolo
oggetto modificato, applicare le modifiche richieste ed appenderlo ad
una lista _RecordModificati, o viceversa duplicare l'oggetto modificato,
copiarlo in una lista (_PreModifica) e modificare l'oggetto originale.
Analoghe scelte sono disponibili per le operazioni di inserimento di
nuovi oggetti e di eliminazione di oggetti presenti.
Se vuoi che sia la VCL a fare il tutto crea un ClientDataSet a partire
dalla tua lista e lascia fare a lui (datagrid, dbaware, etc); altrimenti
vai di codice.


 nuovi oggetti e di eliminazione di oggetti presenti.
Visto che gli elementi non sono molti, penso sia piů semplice fare come sto
facendo ora:
- Creo una copia della ista di partenza
- modifico a piacere quella lista
- se accetto le modifiche, sostituisco i dati della lista di partenza con i
dati della copia


 Comunque, quel dest.add, aggiunge solo un riferimento o ne fa una copia?
qualsiasi derivato di TList č una lista di puntatori, quindi viene fatta una 
copia di puntatori


 da TObjectList e dovrebbe essere più semplice, no?
tieni anche presente queste due strade alternative (se non vuoi prendere 
quella del ClientDataSet indicatati da Paolo):
- implementa tutto come derivati da TCollection e TCollectionItem: è 
piuttosto semplice e c'è già pronto il metodo Assign, inoltre puoi salvare 
tutto con il meccanismo di persistenza di EOS su un file XML;
- implementa il tutto derivando da TStringList (o facendo uso di una 
TStringList): ha una comoda proprietà Objects...
Ora non ho tempo di farti un esempio con TCollection ma è davvero semplice 
(anche la persistenza, è questione di poche righe di codice)


guarda... di frettissima.. qui sotto dichiaro la classe col sistema delle 
collection.
Da usarsi così:
var
  Accounts: TAccounts;
begin
  Accounts:= TAccounts.Create(TAccount);
  Accounts.Add.Name:= 'pippo';
end;
Dovrebbe esserci anche l'Assign già pronto. Per la persistenza con gli EOS 
su file XML mi è venuto un dubbio perchè tutte le proprietà devono essere 
published e questa
    property Items[i: Integer]: TAccount read GetItem write SetItem; 
non può esserlo.
++++++++++++++++++++++++++
unit Unit2;
interface
uses
  Classes;
type
  TAccount = class(TCollectionItem)
  private
    FName: string;
  published
    property Name: string read FName write FName;
  end;
  TAccounts = class(TCollection)
  private
    function GetItem(i: Integer): TAccount;
    procedure SetItem(i: Integer; Value: TAccount);
  public
    function Add: TAccount;
    property Items[i: Integer]: TAccount read GetItem write SetItem; 
default;
  end;
implementation
{ TAccounts }
function TAccounts.Add: TAccount;
begin
  Result:= TAccount(inherited Add)
end;
function TAccounts.GetItem(i: Integer): TAccount;
begin
  Result:= TAccount(inherited GetItem(i))
end;
procedure TAccounts.SetItem(i: Integer; Value: TAccount);
begin
  inherited SetItem(i, Value);
end;
end.


 collection.
grazie 1000 per tutto il tempo che mi stai dedicando, appena possibile provo
e vi faccio sapere


 provo e vi faccio sapere
tranqui, se non sei convinto non sentirti obbligato a provare, io butto lě 
solo delle idee


 solo delle idee
Questo WE ho fatto un pň di prove e alla fine...ho optato per una soluzione
gestibile in maniera piů semplice: edito un account alla volta.
grazie comunque a tutti
