Einzelnen Beitrag anzeigen

Der schöne Günther

Registriert seit: 6. Mär 2013
6.159 Beiträge
 
Delphi 10 Seattle Enterprise
 
#1

Den Delegaten nachträglich ändern - Unterschiedliches Verhalten

  Alt 29. Jan 2014, 20:46
Poetischer Titel. Und vielleicht passt es doch besser speziell nach Object-Pascal / Delphi-Language. Ich bin nicht sicher.

Mir ist heute etwas sehr interessantes aufgefallen: Nehmen wir folgenden Codeschnipsel:

Delphi-Quellcode:
   IMessageInterface = interface
   ['{F3B8FE2F-C64F-4E5B-B731-6ABB09CDF4E7}']
      procedure ShowMessage();
   end;

   TMessager = class(TInterfacedObject, IMessageInterface)
      private var
         myMessage: String;
      public
         constructor Create(const myMessage: String);
         procedure showMessage();
   end;

   TContainerClass = class(TInterfacedObject, IMessageInterface)
      private
         function getMessageDelegate(): TMessager;

      private var
         messageDelegate: TMessager;

      // Delegation: IMessageInterface
      property messageInterface: TMessager
      read getmessageDelegate implements IMessageInterface;

      public
         constructor Create();
         procedure createNewDelegateInstance();

    end;
Was passiert? Wir haben eine Klasse TContainerClass welche sich (in ihrem Konstruktor) eine TMessager -Instanz erstellt und die ShowMessage -Methode an diese abschiebt.
Die Methode createNewDelegateInstance() zerstört diese Instanz und erstellt eine neue. Unterscheiden kann man beide: Die erste gibt "Erste", die neue Instanz würde "Zweite" auf den Bildschirm ausgeben.

Soweit alles klar? Interessant finde ich nun, was man tatsächlich bekommt, wenn man sich von TContainerClass nun eine IMessageInterface -Referenz holt. Führen wir folgendes aus:

Delphi-Quellcode:
var
   container: TContainerClass;
   messageIntf: IMessageInterface;
begin
   container := TContainerClass.Create();
   Supports(container, IMessageInterface, messageIntf);
   messageIntf.ShowMessage();
sieht man erwartungsgemäß "Erster" auf dem Bildschirm. Schaut man im Debugger aber auf die Variable messageIntf sieht man dort:
Code:
Name   Wert
messageIntf   TMessager($286B964) as IMessageInterface
. Die Referenz hat überhaupt keinen Bezug mehr zu der TContainerClass -Instanz, wir haben hier direkt die TMessager -Instanz!

Warum ist das so? Hat das bestimmte Gründe?

Jetzt kommt der Punkt, der mich verwirrt: Ändern wir von TConainerClass die Methode getMessageDelegate() dass sie kein Interface (IMessageInterface ) sondern eine Klasse (TMessager ) zurückgibt, läuft die Sache ganz anders ab:
Nach dem Aufruf von Supports(..) haben wir in unserer Interface-Variable eine Referenz auf die TContainerClass -Instanz! Erst durch den folgenden Aufruf von ShowMessage wird die getMessageDelegate -Methode abgearbeitet, die TMessager-Instanz zurückgegeben und auf ihr schließlich ShowMessage aufgerufen.

Konkret bedeutet das:
Führen wir also folgendes (zwei Zeilen mehr) aus:
Delphi-Quellcode:
var
   container: TContainerClass;
   messageIntf: IMessageInterface;
begin
   container := TContainerClass.Create();
   Supports(container, IMessageInterface, messageIntf);
   messageIntf.ShowMessage();

   container.createNewDelegateInstance();
   messageIntf.ShowMessage();
Ergibt das im ersten Fall die Ausgabe
  1. Erster
  2. Erster
Während im zweiten Fall
  1. Erster
  2. Zweiter
ausgegeben wird.


Liest noch jemand mit? Meine Schlussfolgerung, was das konkret bedeutet: Nehmen wir an, äußere Umstände zwingen mein TContainerClass-Objekt nun dazu, dass es keine Message senden kann. Es bekommt von nirgendwo einen TMessager her. Alle sind ausverkauft. Als Ersatz liefert es nun ein Nullobjekt, einen Dummy.

Im ersten Fall lässt sich das nicht machen. Derjenige, der sich zum Zeitpunkt als ich nichts im Angebot hatte einmal eine Interface-Referenz geholt hatte bekommt nie mit, wenn ich denn einmal wieder ein vernünftiges TMessager -Objekt habe: Seine Referenz wird immer auf den Dummy zeigen.
Im zweiten Fall geht das - Nur muss sich mein Nullobjekt zwangsweise von TMessager ableiten. Das hat zahlreiche Probleme und Nachteile.

Folglich: Interface-Delegation an aggregierte Objekte. Und der Delegat ändert sich später. Das kann nicht gut gehen, oder? Oder verbeiße ich mich hier nur zu sehr in das "implements" Keyword? Würde ich für alle Interface-Methoden eine eigene Method Resolution Clause nehmen würde man alle Probleme umgehen. Aber hässlich das ist doch potthässlich. Ist das vom Konzept her insgesamt ein No-Go?
  Mit Zitat antworten Zitat