2009-04-01 9 views
10

¿Es posible pasar el método de la interfaz como parámetros?Pasando el método de la interfaz como parámetro

Estoy intentando algo como esto:

interface 

type 
    TMoveProc = procedure of object; 
    // also tested with TMoveProc = procedure; 
    // procedure of interface is not working ;) 

    ISomeInterface = interface 
    procedure Pred; 
    procedure Next; 
    end; 

    TSomeObject = class(TObject) 
    public 
    procedure Move(MoveProc: TMoveProc); 
    end; 

implementation 

procedure TSomeObject.Move(MoveProc: TMoveProc); 
begin 
    while True do 
    begin 
    // Some common code that works for both procedures 
    MoveProc; 
    // More code... 
    end; 
end; 

procedure Usage; 
var 
    o: TSomeObject; 
    i: ISomeInterface; 
begin 
    o := TSomeObject.Create; 
    i := GetSomeInterface; 
    o.Move(i.Next); 
    // somewhere else: o.Move(i.Prev); 
    // tested with o.Move(@i.Next), @@... with no luck 
    o.Free; 
end; 

Pero no está funcionando porque:

E2010 tipos incompatibles: 'TMoveProc' y 'procedimiento, sin tipo de puntero o parámetro sin tipo'

Por supuesto que puedo hacer un método privado para cada llamada, pero eso es feo. ¿Hay alguna forma mejor?

Delphi 2006


Editar: sé que puedo pasar toda la interfaz, pero luego tengo que especificar el uso de la función. No quiero dos exactamente los mismos procedimientos con una llamada diferente.

Puedo usar el segundo parámetro, pero eso también es feo.

type 
    SomeInterfaceMethod = (siPred, siNext) 

procedure Move(SomeInt: ISomeInterface; Direction: SomeInterfaceMethod) 
begin 
    case Direction of: 
    siPred: SomeInt.Pred; 
    siNext: SomeInt.Next 
    end; 
end; 

Gracias a todos por la ayuda e ideas. La solución limpia (para mi Delphi 2006) es el Visitante de Diego. Ahora estoy usando un envoltorio simple ("feo") (el mío, la misma solución de TOndrej y Aikislave).

Pero la verdadera respuesta es "no hay manera (directa) para aprobar los métodos de la interfaz como parámetros sin algún tipo de proveedor.

+0

El código en TSomeObject.Move parece un caso de uso para un patrón de 'Estrategia'. MoveProc podría ser un método de una clase TAbstractMoveProcStrategy, donde las subclases implementan el comportamiento requerido en el método Move. TMovePredStrategy/TMoveNextStrategy tendría diferentes Move procs. – mjn

Respuesta

1

Aquí hay otra solución que está trabajando en Delphi 20006. Es similar a la idea de @Rafael, pero utilizando interfaces:

interface 

type 
    ISomeInterface = interface 
    //... 
    end; 

    IMoveProc = interface 
    procedure Move; 
    end; 

    IMoveProcPred = interface(IMoveProc) 
    ['{4A9A14DD-ED01-4903-B625-67C36692E158}'] 
    end; 

    IMoveProcNext = interface(IMoveProc) 
    ['{D9FDDFF9-E74E-4F33-9CB7-401C51E7FF1F}'] 
    end; 


    TSomeObject = class(TObject) 
    public 
    procedure Move(MoveProc: IMoveProc); 
    end; 

    TImplementation = class(TInterfacedObject, 
     ISomeInterface, IMoveProcNext, IMoveProcPred) 
    procedure IMoveProcNext.Move = Next; 
    procedure IMoveProcPred.Move = Pred; 
    procedure Pred; 
    procedure Next; 
    end; 

implementation 

procedure TSomeObject.Move(MoveProc: IMoveProc); 
begin 
    while True do 
    begin 
    // Some common code that works for both procedures 
    MoveProc.Move; 
    // More code... 
    end; 
end; 

procedure Usage; 
var 
    o: TSomeObject; 
    i: ISomeInterface; 
begin 
    o := TSomeObject.Create; 
    i := TImplementation.Create; 
    o.Move(i as IMoveProcPred); 
    // somewhere else: o.Move(i as IMoveProcNext); 
    o.Free; 
end; 
+0

Esto es muy prometedor – DiGi

1

no se puede. Debido a la determinación del alcance de las interfaces sería posible (tal vez?) para que se libere la interfaz antes de llamar a la función .Next. Si desea hacerlo, debe pasar toda la interfaz a su método en lugar de solo un método.

Editado ... Disculpe, este próximo bit , específicamente, el bit "De la interfaz" se entendía en broma.

Además, y podría estar equivocado aquí, i.Next no es un método de objeto, según su definición de tipo, ¡sería un método de interfaz!

volver a definir su función

TSomeObject = class(TObject) 
    public 
     procedure Move(Const AMoveIntf: ISomeInterface); 
    end; 

    Procedure TSomeObject.Move(Const AMoveIntf : ISomeInterface); 
    Begin 
     ....; 
     AMoveIntf.Next; 
    end; 

    O.Move(I); 

Espero que esto ayude.

+0

No, no está funcionando - Interfaz esconde objeto :( – DiGi

+0

Procedimiento Mover (Const AInterface: ISomeInterface); Comience .... AInterface.Next; Fin; – Aikislave

4

No sé la razón exacta por la que necesita hacer eso, pero, personalmente, creo que sería mejor pasar todo el objeto "Mover" en lugar de uno de sus métodos. Usé este enfoque en el pasado, se llama patrón de "visitante". tiOPF, un marco de persistencia de objetos, lo usa ampliamente y le da un buen ejemplo de cómo funciona: The Visitor Pattern and the tiOPF.

Es relativamente largo, pero me resultó muy útil, incluso cuando no usé tiOPF. Observe el paso 3 en el documento, titulado "Paso # 3. En lugar de pasar un puntero de método, pasaremos un objeto".

DiGi, para responder a su comentario: Si usas Patrón de visitante, entonces no tienes una interfaz que implemente varios métodos, sino solo uno (Ejecutar).Luego tendría una clase para cada acción, como TPred, TNext, TSomething, y pasaría una instancia de dichas clases al objeto que se procesará. De esta manera, no tiene que saber a qué llamar, solo debe llamar a "Visitante.Ejecutar", y hará el trabajo.

Aquí puede encontrar un ejemplo básico:

interface 

type 
TVisited = class; 

TVisitor = class 
    procedure Execute(Visited: TVisited); virtual; abstract; 
end; 

TNext = class(TVisitor) 
    procedure Execute (Visited: TVisited); override; 
end; 

TPred = class(TVisitor) 
    procedure Execute (Visited: TVisited); override; 
end; 

TVisited = class(TPersistent) 
public 
    procedure Iterate(pVisitor: TVisitor); virtual; 
end; 

implementation 

procedure TVisited.Iterate(pVisitor: TVisitor); 
begin 
    pVisitor.Execute(self); 
end; 

procedure TNext.Execute(Visited: TVisited); 
begin 
    // Implement action "NEXT" 
end; 

procedure TPred.Execute(Visited: TVisited); 
begin 
    // Implement action "PRED" 
end; 

procedure Usage; 
var 
    Visited: TVisited; 
    Visitor: TVisitor; 
begin 
    Visited := TVisited.Create; 
    Visitor := TNext.Create; 

    Visited.Iterate(Visitor); 
    Visited.Free; 
end; 
+0

Dado que la función Mover no sabe qué método debe llamar – DiGi

+0

Solución más limpia – DiGi

-1

Actualmente, tienes TMoveProc definido como

TMoveProc = procedure of object; 

Pruebe a retirar el "objeto" que implica un oculto puntero "this" como primera parámetro.

TMoveProc = procedure; 

Eso debería permitir que se llame a un procedimiento normal.

+0

Eso tampoco funciona ... Actualizaré mi pregunta – DiGi

2

¿Qué tal esto:

type 
    TMoveProc = procedure(const SomeIntf: ISomeInterface); 

    TSomeObject = class 
    public 
    procedure Move(const SomeIntf: ISomeInterface; MoveProc: TMoveProc); 
    end; 

procedure TSomeObject.Move(const SomeIntf: ISomeInterface; MoveProc: TMoveProc); 
begin 
    MoveProc(SomeIntf); 
end; 

procedure MoveProcNext(const SomeIntf: ISomeInterface); 
begin 
    SomeIntf.Next; 
end; 

procedure MoveProcPred(const SomeIntf: ISomeInterface); 
begin 
    SomeIntf.Pred; 
end; 

procedure Usage; 
var 
    SomeObj: TSomeObject; 
    SomeIntf: ISomeInterface; 
begin 
    SomeIntf := GetSomeInterface; 
    SomeObj := TSomeObject.Create; 
    try 
    SomeObj.Move(SomeIntf, MoveProcNext); 
    SomeObj.Move(SomeIntf, MoveProcPred); 
    finally 
    SomeObj.Free; 
    end; 
end; 
+0

Ese es mi "Por supuesto que puedo hacer un método privado para cada llamada, pero eso es feo" ... – DiGi

6

Si estaba utilizando Delphi 2009, se puede hacer esto con un método anónimo:

TSomeObject = class(TObject) 
public 
    procedure Move(MoveProc: TProc); 
end; 

procedure Usage; 
var 
    o: TSomeObject; 
    i: ISomeInterface; 
begin 
    o := TSomeObject.Create; 
    i := GetSomeInterface; 
    o.Move(procedure() begin i.Next end); 

El problema con tratar de pasar una referencia sólo a la interfaz método es que no está pasando una referencia a la interfaz en sí, por lo que la interfaz no puede ser contada de referencia. Pero los métodos anónimos son ellos mismos referencia contada, por lo que la referencia de la interfaz dentro del método anónimo aquí también puede ser contada como referencia. Es por eso que este método funciona.

+1

OK; vas a tiene que vivir con "feo", luego (¡o actualizar!), porque un método "no feo" que rompe el conteo de referencias sería peor. –

3

Aunque la solución de la clase contenedora funciona, creo que es una exageración. Es demasiado código, y usted tiene que administrar manualmente la vida del nuevo objeto.

Tal vez una solución más sencilla sería la creación de métodos de la interfaz que devuelve TMoveProc

ISomeInterface = interface 
    ... 
    function GetPredMeth: TMoveProc; 
    function GetNextMeth: TMoveProc; 
    ... 
end; 

La clase que implementa la interfaz puede proporcionar la procedure of object y será accesible a través de la interfaz.

TImplementation = class(TInterfaceObject, ISomeInterface) 
    procedure Pred; 
    procedure Next; 

    function GetPredMeth: TMoveProc; 
    function GetNextMeth: TMoveProc; 
end; 

... 

function TImplementation.GetPredMeth: TMoveProc; 
begin 
    Result := Self.Pred; 
end; 

function TImplementation.GetNextMeth: TMoveProc; 
begin 
    Result := Self.Next; 
end; 
+0

+1 Esto funciona. Pero hay que tener cuidado, que la referencia a la implementación objeto todavía existe en el momento, la función se llama . Para una solución que funciona de manera similar pero que atiende las referencias, vea [mi respuesta] (http: // stackoverflow.com/a/24392902/2306907) – yonojoy

Cuestiones relacionadas