2009-04-20 15 views
9

Para un problema particular en la arquitectura de una aplicación en la que estoy trabajando, las interfaces parecen ser una buena solución. Específicamente, algunos "objetos comerciales" dependen de una serie de configuraciones que se extraen de la base de datos en la aplicación real. Dejar que los objetos de negocio piden una interfaz (a través Inversión de Control), y dejando un TDatabaseSettings objeto central implementar esas interfaces, permite un mejor aislamiento, y por lo tanto para las pruebas unitarias mucho más fácil.Anulación (deshabilitación) Conteo de referencias de Delphi para las interfaces

Sin embargo, en Delphi, las interfaces parecen venir con una, en este caso, la prima desagradable: el recuento de referencias. Esto significa que si hago algo como esto:

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; 

En la última línea (O.Free), mi DatabaseSettings objeto global es ahora también liberado, ya que se pierde la última referencia de la interfaz a la misma (que estaba contenido en O) !

Una solución sería la de almacenar la DatabaseSettings objeto 'global' con una interfaz; Otra solución sería anular el mecanismo de recuento de referencias para la clase TDatabaseSettings, por lo que puedo continuar administrando el DatabaseSettings como un objeto normal (que es mucho más consistente con el resto de la aplicación).

Así que, en resumen, mi pregunta es: ¿cómo desactivo el mecanismo de conteo de referencia de la interfaz para una clase en particular?

he sido capaz de encontrar algo de información que sugiera anulando los métodos IInterface_AddRef y _Release para la clase (TDatabaseSettings en el ejemplo); ¿Alguien ha hecho eso alguna vez?

O habría que decir que no debería hacer esto (confundiendo? Sólo una mala idea?), Y encontrar una solución diferente al problema de arquitectura?

¡Muchas gracias!

Respuesta

13

Ok, se puede prescindir de ella, pero la pregunta es si realmente quiero eso. Si desea utilizar interfaces, es mejor utilizarlas por completo. Entonces, como lo ha experimentado, tiene problemas si mezcla variables de clase y de interfaz.

var 
    // DatabaseSettings: TDatabaseSettings; 
    DatabaseSettings : IMySettings; 

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

Ahora tiene una segunda referencia a la interfaz y la pérdida de la primera no va a liberar el objeto.

Es como también es posible mantener tanto la clase y el objeto:

var 
    DatabaseSettings: TDatabaseSettings; 
    DatabaseSettingsInt : IMySettings; 

Asegúrese de configurar la interfaz de la derecha después de que el objeto ha sido creado.

Si realmente desea desactivar el recuento de referencias, sólo hay que crear un nuevo descendiente de TObject que implementa IInterface.He probado el siguiente ejemplo en D2009 y funciona:

// 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 disminuye el refcount igual _Release. Puede usarlo donde normalmente usa Gratis.

+0

Muchas gracias por la extensa respuesta, ¡es muy apreciada! Sí, probablemente debería pensar un poco más antes de ir por el camino oscuro de deshabilitar el conteo de referencias. – onnodb

+0

Ok, lo probé en D2009 y funcionó a las mil maravillas ;-). –

+0

¿Tengo razón en que su ejemplo no deshabilita completamente el recuento de referencias, sino que hace que el recuento de referencias comience en 1? Supongo que también podrías lograr eso llamando explícitamente a "TMyInterfacedObject._AddRef" justo después de la creación del objeto, y luego haciendo un "_Release" donde normalmente llamarías "Gratis". – onnodb

7

_AddRef, _Release y _QueryInterface son, de hecho, lo que desea anular. Sin embargo, debes tener muy claro lo que estás haciendo, ya que esto puede causar pérdidas de memoria o errores extraños y difíciles de encontrar.

no descienden del TInterfacedObject, en lugar de descender TObject, y poner en práctica sus propias versiones de los dos primeros de esos métodos que devuelven 1.

+1

Eso es realmente rápido ... ¡muchas gracias! ¿Crees que la forma en que quiero usar interfaces tiene sentido? Me parece extraño que todo el recuento de referencias entre, en realidad, ¿por qué no usarlo como una manera realmente agradable de desacoplar clases? – onnodb

+0

El recuento de referencias es en realidad muy astuto. No necesita liberar el objeto; Delphi lo hará por usted cuando la variable a la que está asignado caiga fuera del alcance. –

+1

Sí, eso es cierto, * es * resbaladizo (aunque todavía no he encontrado un uso). Pero también es bueno poder deshabilitarlo, ya que hace las cosas incoherentes si se combina con la administración de objetos 'tradicional' :) – onnodb

3

Desactivar el conteo de referencias para este tipo de problemas huele mal. Una solución mucho más agradable y arquitectónica sería utilizar algún tipo de patrón "singleton". La forma más fácil de poner en práctica este sería el resultado:

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; 

Por cierto: cuando se utiliza interfaces: utilice siempre variables de la interfaz! No mezcle ambas clases y variables de interfaz (use "var Settings: IMySettings" en lugar de "var Settings: TDatabaseSettings"). De lo contrario, el recuento de referencias se interpondrá en su camino (autodestrucción, operaciones de puntero no válidas, etc.). En la solución anterior, GDatabaseSettings también es del tipo "IMySettings", por lo que obtiene un recuento de referencia adecuado y durará hasta que el programa finalice.

+0

Sí, huele, y todos aquí parecen estar de acuerdo en eso. Lo cual es una lástima, ya que las interfaces parecen una buena forma de desacoplar objetos (usándolos solo como un "contrato"). ¡Gracias por tu contribución! – onnodb

+0

@onnodb: Las interfaces son realmente una buena ayuda para el diseño desacoplado correctamente. A menos que necesite referencias circulares, el conteo de ref no es nada malo; solo debe tener cuidado al mezclar interfaces con referencias a los objetos de implementación. No hagas esto, y serás feliz. Como las clases pueden implementar múltiples interfaces, generalmente no hay necesidad de usar las referencias de objeto directamente, solo implemente y use una interfaz secundaria más especializada. – mghie

+2

Otro consejo: No uses la técnica en esta respuesta, todos los lados negativos de los singleton se aplican aquí también - si tratas de eliminar los globales, no busques estos globals disfrazados. Tener una referencia de interfaz en un lugar que controle es tan buena como la técnica anterior, con la ventaja añadida de que el objeto de implementación se destruirá una vez que se restablezcan todas las referencias a él, en lugar de permanecer en la memoria hasta que finalice la aplicación. Eso sería un poco mejor que una pérdida de memoria. – mghie

4

Para desactivar el recuento de referencias, AddRef y liberación debe hacer nada más que devolver -1

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

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

Hay un buen montón de utilidad en las interfaces sin recuento de referencias. Si usa el recuento de referencias, entonces no puede mezclar referencias de objetos y de interfaz ya que sucederán cosas malas. Al deshabilitar los conteos de ref, puede felizmente mezclar referencias de objetos y de interfaz sin preocuparse de que sus objetos se destruyan automáticamente.

+2

Una advertencia con este enfoque: asegúrese absolutamente de haber borrado todas las referencias de interfaz a un objeto antes de que el objeto sea Free'd, de lo contrario, obtendrá errores extraños. –

0

o simplemente utilizar el código de abajo:

 
    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

no descienden del TInterfacedObject, en lugar de descender TSingletonImplementation de unidad estándar System.Generics.Defaults.

  • TSingletonImplementation es una base para las clases simples que necesitan una implementación básica IInterface, con referencia contando con discapacidad.
  • TSingletonImplementation es una clase base segura para subprocesos para clases Delphi que admiten interfaces. A diferencia de TInterfacedObject, TSingletonImplementation no implementa el recuento de referencias.
+0

Buena respuesta, pero algo que no estaba disponible en la biblioteca estándar en mi versión de Delphi en ese momento (<= 2007) :) – onnodb

Cuestiones relacionadas