2010-12-07 19 views
11

Suponga que tiene el siguiente:Herencia de interfaz Delphi: ¿Por qué no puedo acceder a los miembros de la interfaz ancestro?

//Note the original example I posted didn't reproduce the problem so 
//I created an clean example 
    type 
    IParent = interface(IInterface) 
    ['{85A340FA-D5E5-4F37-ABDD-A75A7B3B494C}'] 
     procedure DoSomething; 
    end; 

    IChild = interface(IParent) 
    ['{15927C56-8CDA-4122-8ECB-920948027015}'] 
     procedure DoSomethingElse; 
    end; 

    TGrandParent = class(TInterfacedObject) 
    end; 

    TParent = class(TGrandParent) 
    end; 

    TChild = class(TParent, IChild) 
    private 
     FChildDelegate: IChild; 
    public 
     property ChildDelegate:IChild read FChildDelegate implements IChild; 
    end; 

    TChildDelegate = class(TInterfacedObject, IChild) 
    public 
     procedure DoSomething; 
     procedure DoSomethingElse; 
    end; 

Me gustaría pensar que esto permitiría que llame DoSomething pero esto no parece ser el caso:

procedure CallDoSomething(Parent: TParent); 
begin 
    if Parent is TChild then 
    TChild(Parent).DoSomething; 
end; 

Su claro que es el compilador imponiendo la herencia de la interfaz porque ninguna clase se compilará a menos que se implementen los miembros de IParent. A pesar de esto, el compilador no puede resolver los miembros del IParent cuando se crea una instancia de la clase y se utiliza.

puedo evitar esto incluyendo explícitamente IParent en la declaración de clase de TMyClass:

TMyClass = class(TInterfacedObject, IChild, IParent) 

No importa, esto no funciona en torno a cualquier cosa.

+1

¿Podemos suponer que FObject debería ser FChild? –

+0

@Lieven, gracias. Perdí ese. Copié de la fuente original y cambié los nombres para hacerlo más claro. –

+2

Después de cambiar "propiedad Objeto" a, por ejemplo, "propiedad Obj" el código se compila en Delphi 2009 – kludg

Respuesta

8

El problema no está en las declaraciones de interfaz o implementaciones de clase, pero en su código de consumo:

procedure CallDoSomething(Parent: TParent); 
begin 
    if Parent is TChild then 
    TChild(Parent).DoSomething; // << This is wrong 
end; 

no va a funcionar porque TChild no tiene un método "HacerAlgo". Si TChild implementado iChilddirectamente, entonces esto sería normalmente ser posible porque TChild sería implementar el método directamente Y como parte de la interfaz deiChild.

Obsérvese, sin embargo, que si TChild implementado HacerAlgo en PRIVADO alcance, seguiría siendo accesibles a través de las reglas de alcance de interfaces sino normales significaría que todavía no se podía invocarlo (desde fuera de la clase/UNI) utilizando una referencia TChild tampoco.

En su caso, sólo hay que obtener la interfaz apropiada y luego invocar el método que necesite a través de la interfaz:

if Parent is TChild then 
    (Parent as IChild).DoSomething; 

Sin embargo, está utilizando una prueba de tipo de clase para determinar (inferir) la presencia de una interfaz, basándose en un detalle de implementación (conocimiento de que TChild implementa IChild). Le sugiero que en lugar debería estar utilizando pruebas de la interfaz directa, para aislar esta dependencia de los detalles de implementación:

var 
    parentAsChild: IChild; 

    begin 
    if Parent.GetInterface(IChild, parentAsChild) then 
     parentAsChild.DoSomething; 
    end; 
+1

Este es el problema real. Siguiendo su sugerencia, déjeme descubrir que el punto de llamada está muy unido a la clase llamada y que la interfaz debe expandirse. Tal es la vida al mantener el código de otra persona. –

0

editar: Esta respuesta ya no es relevante porque se publicó antes de que se modificara la pregunta original.


Esto compila en Delphi 2010:

type 
    IParent = interface(IInterface) 
    function DoSomething: String; 
    end; 

    IChild = interface(IParent) 
    function DoSomethingElse: string; 
    end; 

    TMyClass = class(TInterfacedObject, IChild) 
    private 
    public 
    function DoSomething: String; 
    function DoSomethingElse: String; 
    end; 

// ... 

procedure Test; 
var 
    MyObject : IChild; 
begin 
    MyObject := TMyClass.Create; 
    MyObject.DoSomething; 
end; 
+0

el problema está en la cláusula * implementa *. Lo que escribió es esencialmente lo mismo que la clase TChild OP escribió. –

+0

Mi respuesta fue relevante para la publicación original antes de que la editara.En ese código de ejemplo, declaró las variables como IChild y obtuvo un error de compilación cuando intentó llamar a DoSomething. –

11

Si una clase de aplicación no podrá declarar que soporta una interfaz heredada, entonces la clase no será compatible con la asignación de las variables de la interfaz heredada. El ejemplo de código que publicó debe funcionar bien (utilizando la interfaz IChild), pero si intenta asignar desde una instancia de TMyClass a una variable de IParent, entonces tendrá problemas.

La razón es porque COM y ActiveX permiten que una implementación implemente una interfaz descendiente (su IChild) pero niega el antecesor de esa interfaz (IParent). Dado que las interfaces Delphi están destinadas a ser compatibles con COM, de ahí viene este artefacto tonto.

Estoy bastante seguro de que escribí un artículo sobre esto hace unos 10 o 12 años, pero mi blog de Borland no sobrevivió a la transición al servidor de Embarcadero.

Puede haber una directiva de compilación para cambiar este comportamiento, no recuerdo.

+0

@dthorpe - por lo que la afirmación que crecí con esa herencia de interfaz es solo azúcar sintáctica para reducir el tipeo no es del todo correcto ?! –

+0

Este es probablemente el problema. La clase real es un descendiente de una jerarquía de clases que proporciona implementaciones predeterminadas para algunas de las interfaces. Se pasa a una función como clase ancestra y luego se envía a la clase descendiente antes de que se llame a 'DoSomething'. –

+0

@Lieven: Nada tocado por COM es el azúcar sintáctico "solo". : P – dthorpe

-1

La aplicación de Delphi QueryInterface no está a la altura. En la entrada del blog titulada The ways people mess up IUnknown::QueryInterface, Raymond Chen enumera fallas comunes en la implementación. Lo más notable es el tercer punto

Olvidarse de responder a las interfaces de base. Cuando implementa una interfaz derivada, implementa implícitamente las interfaces base, por lo que no se olvide de responderlas también.

IShellView *psv = some object; 
IOleView *pow; 
psv->QueryInterface(IID_IOleView, (void**)&pow); 

Algunos objetos se olvidan y QueryInterface falla con E_NOINTERFACE.

A menos de una interfaz heredada está unido de forma explícita a una clase o una de sus antepasados ​​Delphi no puede encontrarla. Simplemente atraviesa la tabla de interfaz del objeto y sus tipos heredados y busca una coincidencia de los ID de interfaz, no comprueba las interfaces base.

+0

Este análisis es erróneo. Danny explicó que Delphi estaba trabajando en algunas rarezas del código MS cuando diseñaron esto. La implementación de QI es en realidad una combinación de cosas. Lo implementa especificando las interfaces que admite y confiando en la QI proporcionada en TInterfacedObject. No piense que esa función es la única parte de su implementación de QI. Se basa en las interfaces para las que declaras soporte. Es su trabajo enumerar a los antepasados. –

+0

@DavidHeffernan He leído y entendido por qué trabajaron con el error 'IClassFactory2', pero también rompieron el concepto de que una clase que implementa interfaz derivada implementa implícitamente su antecesor. –

+0

No están implementando QI. Usted está. Por las interfaces que declaras para ser implementado. –

Cuestiones relacionadas