2012-02-01 5 views
7

He leído con interés el blog de Nick Hodges en Why You Should Be Using Interfaces y como ya estoy enamorado de las interfaces en un nivel superior en mi codificación, decidí ver cómo podría extender esto bastante bajos niveles e investigar qué soporte para esto existía en las clases de VCL.Código contra una interfaz con TStrings y TStringList

Una construcción común que necesito es hacer algo sencillo con un TStringList, por ejemplo, el código para cargar una pequeña lista de archivos de texto en una cadena de texto coma:

var 
    MyList : TStrings; 
    sCommaText : string; 
begin 
    MyList := TStringList.Create; 
    try 
    MyList.LoadFromFile('c:\temp\somefile.txt'); 
    sCommaText := MyList.CommaText; 

    // ... do something with sCommaText..... 

    finally 
    MyList.Free; 
    end; 
end; 

parecería un buen simplificación si podría escribir con el uso de MyList como interfaz - sería deshacerse de la legibilidad try-finally y mejorar:

var 
    MyList : IStrings; 
     //^^^^^^^ 
    sCommaText : string; 
begin 
    MyList := TStringList.Create; 
    MyList.LoadFromFile('c:\temp\somefile.txt'); 
    sCommaText := MyList.CommaText; 

    // ... do something with sCommaText..... 

end; 

no puedo ver un IStrings definen sin embargo - ciertamente no en Classes.pas, aunque hay referencias a él en relación con la programación OLE en línea. ¿Existe? ¿Es esto una simplificación válida? Estoy usando Delphi XE2.

+4

Si quieres mi opinión: ¡no hagas eso! Del mismo modo que nunca utilizar interfaces no es una solución, el uso de interfaces para todo tampoco lo es. Incluso Nick afirma específicamente TStrings/TStringList para ser utilizado perfectamente como instancias de clase en su última publicación de blog. –

+4

TStrings es "casi" una interfaz, es una clase abstracta que podría tener diferentes implementaciones. Siempre que sea posible, solo paso TStrings como tipo de parámetros, en lugar de TStringList. – mjn

+0

Estoy de acuerdo con @UweRaabe. Las interfaces son poderosas y los principiantes hacen un uso indebido de herramientas poderosas. No use una referencia de interfaz en lugar de una referencia de objeto simplemente porque eso es posible. Recomiendo cumplir con el propósito original de las interfaces: abrir diseño extensible. – kludg

Respuesta

6

No hay interfaz en el RTL/VCL que haga lo que quiera (exponer la misma interfaz que TStrings). Si quisiera usar tal cosa, tendría que inventarlo usted mismo.

Se podría aplicar con un envoltorio de la siguiente manera:

type 
    IStrings = interface 
    function Add(const S: string): Integer; 
    end; 

    TIStrings = class(TInterfacedObject, IStrings) 
    private 
    FStrings: TStrings; 
    public 
    constructor Create(Strings: TStrings); 
    destructor Destroy; override; 
    function Add(const S: string): Integer; 
    end; 

constructor TIStrings.Create(Strings: TStrings); 
begin 
    inherited Create; 
    FStrings := Strings; 
end; 

destructor TIStrings.Destroy; 
begin 
    FStrings.Free; // don't use FreeAndNil because Nick might see this code ;-) 
    inherited; 
end; 

function TIStrings.Add(const S: string): Integer; 
begin 
    Result := FStrings.Add(S); 
end; 

Naturalmente que sería concluir el resto de la interfaz TStrings en una clase real. Hazlo con una clase contenedora como esta para que puedas envolver cualquier tipo de TStrings solo teniendo acceso a una instancia de este.

utilizar de esta manera:

var 
    MyList : IStrings; 
.... 
MyList := TIStrings.Create(TStringList.Create); 

Es posible que prefiera para agregar una función de ayuda a hacer realidad el trabajo sucio de llamar TIStrings.Create.

Tenga en cuenta también que la duración podría ser un problema. Es posible que desee una variante de este contenedor que no se haga cargo de la administración de la instancia subyacente TStrings. Eso podría arreglarse con un parámetro de constructor TIStrings.


Yo mismo, creo que este es un experimento de pensamiento interesante pero no es realmente un enfoque sensato. La clase TStrings es una clase abstracta que tiene prácticamente todos los beneficios que ofrecen las interfaces. No veo inconvenientes reales de usarlo tal cual.

+4

¿Por qué no crear simplemente una propiedad TIStrings.Strings: TStrings predeterminada para acceder sin tener que para envolver todos los métodos? –

+1

@arnaud que haría el código mucho más torpe porque tendrías que escribir 'Strings' mucho o almacenarlo en un local. Una propiedad predeterminada solo evita eso para las propiedades de la matriz. Pero si todo lo que deseaba era un emulador RAII que sería una buena forma de hacerlo, supongo que –

+1

+1 "no es el enfoque sensato".8-) – mj2008

4

Dado que TStrings es una clase abstracta, una versión de interfaz no proporcionaría mucho. Cualquier implementador de esa interfaz seguramente sería un descendiente TStrings de todos modos, porque nadie querría volver a implementar todas las cosas que hace TStrings. Veo dos razones para querer una interfaz TStrings:

  1. la limpieza automática de recursos. No necesita una interfaz específica TStrings para eso. En su lugar, use la interfaz ISafeGuard del JCL.He aquí un ejemplo:

    var 
        G: ISafeGuard; 
        MyList: TStrings; 
        sCommaText: string; 
    begin 
        MyList := TStrings(Guard(TStringList.Create, G)); 
    
        MyList.LoadFromFile('c:\temp\somefile.txt'); 
        sCommaText := MyList.CommaText; 
    
        // ... do something with sCommaText..... 
    end; 
    

    Para proteger varios objetos que deben tener el mismo tiempo de vida, utilizar IMultiSafeGuard.

  2. Interoperación con módulos externos. Esto es para lo que IStrings es. Delphi lo implementa con la clase TStringsAdapter, que se devuelve cuando llamas al GetOleStrings en un descendiente TStrings existente. Úselo cuando tenga una lista de cadenas y necesite otorgar acceso a otro módulo que espera las interfaces IStrings o IEnumString. Esas interfaces son torpes para usar de otra manera, ninguna de las dos proporciona todas las cosas que hace TStrings, así que no las use a menos que sea necesario.

    Si el módulo externo que está trabajando es algo que se puede garantía siempre será elaborado con la misma versión de Delphi que su módulo se compila con, entonces debe usar paquetes de tiempo de ejecución y pasar directamente TStrings descendientes . El paquete compartido permite que ambos módulos usen la misma definición de la clase, y la administración de la memoria se simplifica enormemente.

+0

'ISafeGuard' está pidiendo a gritos genéricos y seguridad de tipo. También es bastante trivial escribir. Si aún no está utilizando JCL, probablemente no valga la pena tomar una dependencia de JCL solo para lograrlo. –

+0

@Rob: +1 para una solución intrigante. ¡Académicamente fascinante pero propinablemente no explicable para mis compañeros de trabajo! –

Cuestiones relacionadas