2009-04-20 17 views
9

Per un problema particolare nell'architettura di un'applicazione su cui sto lavorando, le interfacce sembrano essere una buona soluzione. Nello specifico, alcuni "oggetti business" dipendono da una serie di impostazioni estratte dal database nell'app reale. Lasciando che questi oggetti business richiedano un'interfaccia (tramite Inversion of Control) e consentendo a un oggetto centrale TDatabaseSettings di implementare tali interfacce, consente un migliore isolamento e, quindi, un collaudo dell'unità molto più semplice.Bypassare (disabilitare) il conteggio dei riferimenti di Delphi per le interfacce

Tuttavia, in Delphi, le interfacce sembrano venire con, in questo caso, un bonus spiacevole: il conteggio dei riferimenti. Questo vuol dire che se faccio qualcosa di simile:

type 
IMySettings = interface 
    function getMySetting: String; 
end; 

TDatabaseSettings = class(..., IMySettings) 
    //... 
end; 

TMyBusinessObject = class(TInterfacedObject, IMySettings) 
    property Settings: IMySettings read FSettings write FSettings; 
end; 

var 
    DatabaseSettings: TDatabaseSettings; 
    // global object (normally placed in a controller somewhere) 

//Now, in some function... 
O := TMyBusinessObject.Create; 
O.Settings := DatabaseSettings; 
// ... do something with O 
O.Free; 

Sull'ultima riga (O.Free), la mia globale DatabaseSettings oggetto è ora anche liberato, dal momento che l'ultimo riferimento di interfaccia ad esso (che è stata contenuta in O) è persa !

Una soluzione sarebbe quella di memorizzare l'oggetto "globale" DatabaseSettings con un'interfaccia; un'altra soluzione sarebbe quella di ignorare il meccanismo di conteggio dei riferimenti per la classe TDatabaseSettings, quindi posso continuare a gestire lo DatabaseSettings come un oggetto normale (che è molto più coerente con il resto dell'app).

Quindi, in sintesi, la mia domanda è: come disattivare il meccanismo di conteggio dei riferimenti dell'interfaccia per una particolare classe?

Sono stato in grado di trovare alcune informazioni che suggerisce sovrascrivendo i metodi IInterface_AddRef e _Release per la classe (TDatabaseSettings nell'esempio); qualcuno lo ha mai fatto?

Oppure diresti che non dovrei farlo (confondendo? Solo una cattiva idea?), E trovare una soluzione diversa al problema architettonico?

Grazie mille!

risposta

13

Ok, è possibile ignorare, ma la domanda è se davvero voglio quello. Se si desidera utilizzare le interfacce, è meglio usarle completamente. Così come l'hai sperimentato, hai problemi se mischia le variabili di classe e di interfaccia.

var 
    // DatabaseSettings: TDatabaseSettings; 
    DatabaseSettings : IMySettings; 

//Now, in some function... 
O := TMyBusinessObject.Create; 
O.Settings := DatabaseSettings; 
// ... do something with O 
O.Free; 

Ora hai un secondo riferimento all'interfaccia e perdere il primo non libererà l'oggetto.

E anche per quanto possibile, di mantenere sia la classe e l'oggetto:

var 
    DatabaseSettings: TDatabaseSettings; 
    DatabaseSettingsInt : IMySettings; 

assicurarsi di impostare l'interfaccia destra dopo che l'oggetto è stato creato.

Se si desidera veramente disattivare il conteggio dei riferimenti, è sufficiente creare un nuovo discendente di TObject che implementa IInterface.Ho testato l'esempio di seguito in D2009 e funziona:

// Query Interface can stay the same because it does not depend on reference counting. 
function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult; 
begin 
    if GetInterface(IID, Obj) then 
    Result := 0 
    else 
    Result := E_NOINTERFACE; 
end; 

constructor TMyInterfacedObject.Create; 
begin 
    FRefCount := 1; 
end; 

procedure TMyInterfacedObject.FreeRef; 
begin 
    if Self = nil then 
    Exit; 
    if InterlockedDecrement(FRefCount) = 0 then 
    Destroy;  
end; 

function TMyInterfacedObject._AddRef: Integer; 
begin 
    Result := InterlockedIncrement(FRefCount); 
end; 

function TMyInterfacedObject._Release: Integer; 
begin 
    Result := InterlockedDecrement(FRefCount); 
    if Result = 0 then 
    Destroy; 
end; 

FreeRef solo abbassa la refcount proprio come _Release. Puoi usarlo dove normalmente usi Free.

+0

Grazie mille per la risposta molto ampia, è molto apprezzata! Sì, probabilmente dovrei pensare un po 'di più prima di passare al buio del percorso di disattivazione del conteggio dei riferimenti. – onnodb

+0

Ok, l'ho provato in D2009 e ha funzionato come un fascino ;-). –

+0

Ho ragione che il tuo esempio non disabilita completamente il conteggio dei riferimenti, ma invece fa partire il conteggio dei riferimenti a 1? Immagino che potresti anche farlo chiamando esplicitamente "TMyInterfacedObject._AddRef" subito dopo la creazione dell'oggetto, e poi facendo un "_Release" dove normalmente chiameresti "Libero"? – onnodb

7

_AddRef, _Release e _QueryInterface sono, infatti, ciò che si desidera ignorare. Dovresti essere molto chiaro su ciò che stai facendo, tuttavia, poiché ciò può causare perdite di memoria o strani bug difficili da trovare.

non discendono da TInterfacedObject, invece, scenderà dal TObject, e mettere in atto le proprie versioni dei primi due di quei metodi che restituiscono 1.

+1

Questo è davvero veloce ... grazie mille! Pensi che il modo in cui voglio usare le interfacce abbia senso? Mi sembra strano che l'intero conteggio dei riferimenti entri per davvero, in realtà --- perché non usarli come un modo davvero carino per disaccoppiare le classi? – onnodb

+0

Il conteggio dei riferimenti è in realtà molto scorrevole. Non è necessario liberare l'oggetto; Delphi lo farà per te quando la variabile a cui è assegnata non rientra nello scope. –

+1

Sì, è vero, * è * slick (anche se non ne ho ancora trovato l'uso). Ma è anche bello poterlo disabilitare, poiché rende le cose incoerenti se mescolato con la gestione degli oggetti "tradizionale" :) – onnodb

3

La disattivazione del conteggio dei riferimenti per questo tipo di problema ha un cattivo odore. Una soluzione molto più bella e architettonica sarebbe quella di utilizzare una sorta di modello "singleton". Il modo più semplice per implementare questa sarà simile:

interface 

type 

TDatabaseSettings = class(..., IMySettings) 
end; 

function DatabaseSettings: IMySettings; 

implementation 

var 
    GDatabaseSettings: IMySettings; 

function DatabaseSettings: IMySettings; 
begin 
if GDatabaseSettings = nil then GDatabaseSettings := TDatabaseSettings.Create; 
Result := GDatabaseSettings; 
end; 

O := TMyBusinessObject.Create; 
O.Settings := DatabaseSettings; 
O.Free; 

A proposito: quando si utilizza interfacce: usare sempre variabili dell'interfaccia! Non mischiare entrambe le librerie di interfaccia di classe en (usa "var Settings: IMySettings" invece di "var Settings: TDatabaseSettings"). Altrimenti il ​​conteggio dei riferimenti si intrometterà (distruzione automatica, operazioni di puntatore non valide, ecc.). Nella soluzione di cui sopra, GDatabaseSettings è anche di tipo "IMySettings", quindi ottiene un conteggio dei riferimenti appropriato e durerà fino alla fine del programma.

+0

Sì, odora, e tutti qui sembrano essere d'accordo. Il che è pessimo, dal momento che le interfacce sembrano un bel modo per disaccoppiare gli oggetti (usando solo come "contratto"). Grazie per il tuo contributo! – onnodb

+0

@onnodb: le interfacce sono davvero un valido aiuto per un design correttamente disaccoppiato. A meno che non sia necessario disporre di riferimenti circolari, il conteggio dei ref non è affatto male - basta fare attenzione quando si mescolano le interfacce con i riferimenti agli oggetti di implementazione. Non farlo e sarai felice. Poiché le classi possono implementare più interfacce, in genere non è necessario utilizzare direttamente i riferimenti agli oggetti, basta implementare e utilizzare invece un'interfaccia secondaria più specializzata. – mghie

+2

Un altro suggerimento: non usare la tecnica in questa risposta, tutti i lati negativi dei singleton si applicano anche qui - se si tenta di eliminare i globali, non cercare invece tali globali mascherati. Avere un riferimento all'interfaccia in un luogo che controlli è altrettanto valido della tecnica di cui sopra, con l'ulteriore vantaggio che l'oggetto di implementazione sarà effettivamente distrutto dopo che tutti i riferimenti ad esso sono stati ripristinati, invece di rimanere in memoria fino all'uscita dall'app. Sarebbe poco meglio di una perdita di memoria. – mghie

4

Per disabilitare il conteggio dei riferimenti, AddRef e Release dovrebbero fare altro che tornare -1

function TMyInterfacedObject._AddRef: Integer; 
begin 
    Result := -1; 
end; 

function TMyInterfacedObject._Release: Integer; 
begin 
    Result := -1; 
end; 

C'è un bel po 'di utilità nelle interfacce senza contare riferimento. Se usi il conteggio dei riferimenti, non puoi mescolare i riferimenti a oggetti e interfacce perché accadranno cose brutte. Disabilitando i conteggi ref, è possibile combinare felicemente i riferimenti all'interfaccia e agli oggetti senza preoccuparsi che gli oggetti vengano automaticamente distrutti.

+2

Un avvertimento con questo approccio: assicurati di aver cancellato tutti i riferimenti all'interfaccia su un oggetto prima che l'oggetto sia libero, altrimenti otterrai strani errori. –

0

o semplicemente usare il codice qui sotto:

 
    var 
     I: IMyInterface; 
    begin 
     I := ...; 
     ... 
     Do whatever you want in a scope; 
     Initialize(I); //- this will clear the interface variable without calling the _release. 
    end. 
7

non discendono da TInterfacedObject, invece, scenderà dal TSingletonImplementation da unità standard System.Generics.Defaults.

  • TSingletonImplementation è una base per le classi semplici che necessitano di un'implementazione di base IInterface, con riferimento conteggio disabilitato.
  • TSingletonImplementation è una classe di base thread-safe per le classi Delphi che supportano le interfacce. A differenza di TInterfacedObject, TSingletonImplementation non implementa il conteggio dei riferimenti.
+0

Buona risposta, ma qualcosa che non era disponibile nella libreria standard nella mia versione di Delphi al momento (<= 2007) :) – onnodb

Problemi correlati