2009-04-01 8 views
10

Возможно ли передать интерфейсный метод в качестве параметров?Способ передачи интерфейса в качестве параметра

Я пытаюсь что-то вроде этого:

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; 

Но это не работает, потому что:

E2010 Несовместимые типы: 'TMoveProc' и 'процедуры, нетипизированным указатель или нетипизированным параметра'

Конечно, я могу сделать частный метод для каждого вызова, но это уродливо. Есть ли лучший способ?

Delphi 2006


Edit: Я знаю, что я могу передать весь интерфейс, но тогда я должен указать, какой использовать функцию. Я не хочу двух одинаковых процедур с одним разным вызовом.

Я могу использовать второй параметр, но это тоже уродливо.

type 
    SomeInterfaceMethod = (siPred, siNext) 

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

Спасибо всем за помощь и идеи. Чистым решением (для моего Delphi 2006) является Посетитель Диего. Теперь я использую простую («уродливую») оболочку (мое собственное, то же самое решение от TOndrej и Aikislave).

Но истинный ответ «нет (прямого) способа передать методы интерфейса в качестве параметров без какого-либо провайдера.

+0

Код в TSomeObject.Move выглядит прецедент для «Стратегия». MoveProc может быть методом класса TAbstractMoveProcStrategy, где подклассы реализуют требуемое поведение в методе Move. TMovePredStrategy/TMoveNextStrategy будет иметь разные движения Move. – mjn

ответ

1

Вот другое решение, которое работает в Delphi 20006. Она похожа на идею @Rafael, но с использованием интерфейсов:

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

Это очень перспективный – DiGi

1

Вы не можете. Из-за обзор интерфейсов можно было бы (возможно?), чтобы интерфейс был выпущен до того, как вы вызвали функцию .Next. Если вы хотите сделать это, вы должны передать весь интерфейс вашему методу, а не просто методу.

Отредактировано ... Прошу прощения, этот следующий бит , в частности, бит «Интерфейса» означал в шутку.

Кроме того, я мог ошибаться здесь, i.Next не является методом Object, так как ваш тип def, это будет метод интерфейса!

переопределять функции

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

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

    O.Move(I); 

Надеется, что это помогает.

+0

Нет, он не работает - интерфейс скрывает объект :( – DiGi

+0

Процедура Move (Уст-интерфейса: ISomeInterface); Begin .... AInterface.Next; End; – Aikislave

4

Я не знаю, почему вы должны это делать, но лично я считаю, что лучше всего передать весь объект «Mover» вместо одного из его методов. Я использовал этот подход в прошлом, он называется шаблоном «Посетитель». tiOPF, основа сохранения объектов, использует его широко и дает вам хороший пример того, как он работает: The Visitor Pattern and the tiOPF.

Это относительно долго, но это оказалось очень полезным для меня, даже если я не использовал tiOPF. Обратите внимание на шаг 3 в документе под названием «Шаг № 3. Вместо передачи указателя метода мы пройдем объект».

DiGi, чтобы ответить на ваш комментарий: Если вы используете шаблон посетителя, то у вас нет интерфейса, реализующего несколько методов, но только один (выполнить).Тогда у вас будет класс для каждого действия, например, TPred, TNext, TSomething, и вы передаете экземпляр таких классов в объект, который нужно обработать. Таким образом, вам не нужно знать, как звонить, вы просто называете «Visitor.Execute», и он будет выполнять эту работу.

Здесь вы можете найти простой пример:

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

Поскольку функция Move не знает, какой метод должен вызвать – DiGi

+0

Самое чистое решение – DiGi

-1

Вы в настоящее время у TMoveProc определяется как

TMoveProc = procedure of object; 

Try вынимая «объекта», который предполагает скрытый «это» указатель, как первый параметр.

TMoveProc = procedure; 

Это должно позволить вызвать обычную процедуру.

+0

Это тоже не работает .. Я обновлю свой вопрос – DiGi

2

Как об этом:

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

Это мой «Конечно, я могу сделать частный метод для каждого вызова, но это уродливо» ... – DiGi

6

Если вы используете Delphi 2009, вы можете сделать это с помощью анонимного метода:

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); 

Проблема с попыткой передать ссылку на только интерфейс метод заключается в том, что вы не передаете ссылку на сам интерфейс, поэтому интерфейс не может считаться ссылкой. Но анонимные методы сами подсчитываются, поэтому ссылка на интерфейс внутри анонимного метода здесь также может быть подсчитана. Вот почему этот метод работает.

+1

Хорошо, вы собираетесь придется жить с «уродливым», затем (или обновить!), потому что «не уродливый» метод, который прерывает подсчет ссылок, будет хуже. –

3

Хотя решение класса обертки работает, я думаю, что это перебор. Это слишком много кода, и вам нужно вручную управлять временем жизни нового объекта.

Возможно, более простым решением было бы создание методов в интерфейсе, который возвращает TMoveProc

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

Класс, который реализует интерфейс может обеспечить procedure of object и она будет доступна через интерфейс.

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 Это работает. Но нужно позаботиться о том, чтобы ссылка на реализацию объект все еще существует в то время, функция называется , Для решения, которое работает аналогично, но заботится о ссылках, см. [Мой ответ] (http: // stackoverflow.com/a/24392902/2306907) – yonojoy

Смежные вопросы