Interfaces in Objective-C und das Decorator-Pattern

  • Interfaces in Objective-C und das Decorator-Pattern

    Hallo,


    ich möchte für verschiedene Dateioperationen (Copy, Move, ...) separate Klassen anlegen. All diese Operationen sollen die Methode "-(void) run" implementieren.
    Jetzt möchte ich EINEN Decorator für alle Operationen schreiben welcher beispielsweise einen Log-Eintrag beim Aufruf von "run" erzeugt, egal um welche konkrete Operation es sich handelt und den Aufruf von "run" an eine spezielle Operation weiterreicht.

    Ich komme von PHP und dort würde ich das Ganze mit einem Interface "Operation" lösen welches zum Einen von den konkreten Klassen Move, Copy, ... und zum Anderen von meinem Dekorator OperationLog implementiert wird. Dann würde ich jede neue Operation in ein Objekt von OperationLog im Konstruktor übergeben. Jeder run()-Aufruf wird dann geloggt und entsprechend an die spezielle Operation weitergereicht.

    Gibt es in Objective-C eine elegante Methode das Decorator-Pattern umzusetzen? Oder gibt es einen anderen Weg das sauber zu lösen?
    Wichtig ist mir dass die verschiedenen Operationen nicht mit dem Logging überladen werden. Das Logging soll nur ein Beispiel sein - im Prinzip geht es mir später um Erweiterung der Funktionalität der Operationen, welche für alle Operationen dieselbe sein wird, z.B. auch das Versenden von einem Event 'Operation started'.
  • Du kannst das mit Proxys sehr leicht implementieren. Aber mir scheint in der Tat, dass du hier eher an dem Pattern hängst als an der Problemlösung. Vererbung reicht hier völlig aus, auch wenn es nicht so schick klingt. Okay, ich nenne es Inheritance. Jetzt besser?

    Übrigens gibt es in Objective-C keine Konstruktoren. Du solltest dich in die Sprache einlesen.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?
  • Das geht in Richtung aspekt-orientierte Entwicklung. Amin hat auf seinem Macoun-Vortrag 2009 gezeigt, wie sich sowas umsetzen lässt. Wenn Dir das zuviel Aufwand ist, böte sich auch noch das Action-Target-Pattern an. Noch universeller lässt es sich wahrscheinlich über ein Proxy und NSInvocation umsetzen. Ein Beispiel dazu findest Du hier.

    Es spricht aber auch nichts dagegen, das Decorator-Pattern zu verwenden. Apple setzt es ja auch ein: developer.apple.com/library/io…c/uid/TP40002974-CH6-SW43
    „Meine Komplikation hatte eine Komplikation.“
  • macmoonshine schrieb:

    Das geht in Richtung aspekt-orientierte Entwicklung. Amin hat auf seinem Macoun-Vortrag 2009 gezeigt, wie sich sowas umsetzen lässt. Wenn Dir das zuviel Aufwand ist, böte sich auch noch das Action-Target-Pattern an. Noch universeller lässt es sich wahrscheinlich über ein Proxy und NSInvocation umsetzen. Ein Beispiel dazu findest Du hier.

    Es spricht aber auch nichts dagegen, das Decorator-Pattern zu verwenden. Apple setzt es ja auch ein: developer.apple.com/library/io…c/uid/TP40002974-CH6-SW43

    Na, ja, bei "die run-Methoden der Klassen sollen das machen" ist eigentlich Dekorierung etwas überdynamisiert.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?
  • macmoonshine schrieb:

    Der Decorator funktioniert nur mit der run-Methode. AOP oder eine Proxy-Lösung kannst Du beliebige Methoden anwenden.

    Decortion ist ein Patter. Das funktioniert nicht nur mit einer Methode. Seine Problemstellung ist nur auf die run-Methode bezogen. Deshalb sage ich ja, dass Decoration überdynamisiert ist. Sein Problem ist statisch, nicht Decoration.

    Dynamik hat auch nichts damit zu tun, worauf du es anwendest, sondern, wann du die Entscheidung triffst.

    Jetzt nehmen wir mal die von dir genannten und verwiesenen Beispiele:

    Mein AOP: Dynamisch
    Delegation: Dynamisch
    Proxys: Dynamisch
    Action-Target: Dynamisch.
    Kategorien: Statisch.
    Fast alle von dir genannte Mittel der Dekoration sind dynamisch.

    Was sagt Wiki: "die Dekorierer können zur Laufzeit und sogar nach der Instanzierung ausgetauscht werden." Wieder eindeutig dynamisch.

    Das einzige, was in Objective-C statisch ist, sind Klassenhierarchien einschließlich Protokollen und Kategorien.

    Wie gesagt: Dekoration ist außerordentlich dynmisch. Sein Problem ist statisch. Deshalb ist Dekoration überdynamisiert für sein Problem.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?
  • Ich finde den Ansatz mit der AOP sehr gut und es würde sicherlich mein Problem lösen - aber Ihr habt wahrscheinlich Recht dass es etwas zu viel des Guten ist. Auf jeden Fall ein sehr interessanter und unterhaltsamer Vortrag.

    Mit Vererbung (mag es gerne in Deutsch - also nicht nötig es Inheritance zu nennen ;-)) geht es natürlich auch. Aber: der Code bleibt leserlicher und ist einfacher testbar wenn ich das Objekt - welches in seiner Funktionalität erweitert werden soll - dekoriere anstatt es über Vererbung zu lösen. So meine bisherige Erfahrung -> allerdings in anderen Sprachen.

    Der Einsatz eines Proxies leuchtet mir zunächst nicht ein, da ich kein Objekt brauche welches eine Stellvertreterfunktion einnimmt solange das Originalobjekt nicht verfügbar ist. Will ja eine reine Erweiterung des bestehenden Codes haben indem ich vor der Ausführung von run() allen Interessierten ein preRun() sende und danach ein postRun().

    AOP umgesetzt mit Proxies: blog.jayway.com/2009/03/06/proxy-based-aop-for-cocoa-touch

    Vielleicht doch der richtige Weg - auch wenn ich's noch nicht wirklich verstehe.
  • worker schrieb:

    Will ja eine reine Erweiterung des bestehenden Codes haben indem ich vor der Ausführung von run() allen Interessierten ein preRun() sende und danach ein postRun().

    Das klingt mehr nach einem

    C-Quellcode

    1. -(void) myRunMethod
    2. {
    3. [myApplicationDoingRunStuffNotificationCenter postNotificationName:@"runMethodWillStartNotification" object:self];
    4. // Deine Implementierung der Run-Methode
    5. [myApplicationDoingRunStuffNotificationCenter postNotificationName:@"runMethodDidEndNotification" object:self];
    6. }


    Und alles, was irgendwie irgendwo an irgendeinem Notification Center hängt und auf runMethodWillStartNotification oder runMethodDidEndNotification lauscht wird darüber informiert, dass deine Methode gerade irgendwas macht.
    Daraufhin kann es dann eigenständig auf Grund seiner Programmierung festlegen, was es mit diesen Informationen anfangen will.

    Vergleiche
    Notification Programming Guide
    NSNotification Class Reference
    «Applejack» "Don't you use your fancy mathematics to muddle the issue!"

    Iä-86! Iä-64! Awavauatsh fthagn!

    kmr schrieb:

    Ach, Du bist auch so ein leichtgläubiger Zeitgenosse, der alles glaubt, was irgendwelche Typen vor sich hin brabbeln. :-P
  • worker schrieb:

    Der Einsatz eines Proxies leuchtet mir zunächst nicht ein, da ich kein Objekt brauche welches eine Stellvertreterfunktion einnimmt solange das Originalobjekt nicht verfügbar ist. Will ja eine reine Erweiterung des bestehenden Codes haben indem ich vor der Ausführung von run() allen Interessierten ein preRun() sende und danach ein postRun().

    Ein Proxy sind relativ einfach zu basteln. Ohne Auswertung des Returnwertes geht es ungefähr so:

    Quellcode

    1. @interface LoggingProxy
    2. @property (nontatomic, strong) id target;
    3. @end
    4. @implementation LoggingProxy
    5. @synthesize target;
    6. - (id)forwardInvocation:(NSInvocation *)inInvocation {
    7. NSLog(@"start %@", NSStringFromSelector(inInvocation.selector);
    8. [inInvocation invokeWithTarget:self.target];
    9. NSLog(@"end %@", NSStringFromSelector(inInvocation.selector);
    10. }
    Alles anzeigen

    Das kannst Du dann beispielsweise so verwenden:

    Quellcode

    1. id theProxy = [[LoggingProxy alloc] init];
    2. theProxy.target = ...;
    3. [theProxy run];
    „Meine Komplikation hatte eine Komplikation.“
  • worker schrieb:

    Ich finde den Ansatz mit der AOP sehr gut und es würde sicherlich mein Problem lösen - aber Ihr habt wahrscheinlich Recht dass es etwas zu viel des Guten ist. Auf jeden Fall ein sehr interessanter und unterhaltsamer Vortrag.

    Mit Vererbung (mag es gerne in Deutsch - also nicht nötig es Inheritance zu nennen ;-)) geht es natürlich auch. Aber: der Code bleibt leserlicher und ist einfacher testbar wenn ich das Objekt - welches in seiner Funktionalität erweitert werden soll - dekoriere anstatt es über Vererbung zu lösen. So meine bisherige Erfahrung -> allerdings in anderen Sprachen.

    Der Einsatz eines Proxies leuchtet mir zunächst nicht ein, da ich kein Objekt brauche welches eine Stellvertreterfunktion einnimmt solange das Originalobjekt nicht verfügbar ist. Will ja eine reine Erweiterung des bestehenden Codes haben indem ich vor der Ausführung von run() allen Interessierten ein preRun() sende und danach ein postRun().

    AOP umgesetzt mit Proxies: blog.jayway.com/2009/03/06/proxy-based-aop-for-cocoa-touch

    Vielleicht doch der richtige Weg - auch wenn ich's noch nicht wirklich verstehe.

    Wenn du es sauber machen willst, ist AOP schon richtig. Was dich stört, ist, dass das Logging ein Cross-Concern ist. Dafür ist AOP erfunden worden. Du willst das, willst es aber nicht. ;)

    Ja, man kann das mit Proxys machen. Deshalb wude das hier ja auch erwähnt. Aber das ist dann kein wirkliches AOP mehr. Dort taucht das Proxy bei der Erzeugung auf. Ich will nicht sagen, dass das die Kindergartenlösung ist, aber sie bekommt auch keinen Doktorgrad.

    Für dein Problem ist Subclassing absolut okay.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?
  • macmoonshine schrieb:

    worker schrieb:

    Der Einsatz eines Proxies leuchtet mir zunächst nicht ein, da ich kein Objekt brauche welches eine Stellvertreterfunktion einnimmt solange das Originalobjekt nicht verfügbar ist. Will ja eine reine Erweiterung des bestehenden Codes haben indem ich vor der Ausführung von run() allen Interessierten ein preRun() sende und danach ein postRun().

    Ein Proxy sind relativ einfach zu basteln. Ohne Auswertung des Returnwertes geht es ungefähr so:

    Quellcode

    1. @interface LoggingProxy
    2. @property (nontatomic, strong) id target;
    3. @end
    4. @implementation LoggingProxy
    5. @synthesize target;
    6. - (id)forwardInvocation:(NSInvocation *)inInvocation {
    7. NSLog(@"start %@", NSStringFromSelector(inInvocation.selector);
    8. [inInvocation invokeWithTarget:self.target];
    9. NSLog(@"end %@", NSStringFromSelector(inInvocation.selector);
    10. }
    Alles anzeigen

    Das kannst Du dann beispielsweise so verwenden:

    Quellcode

    1. id theProxy = [[LoggingProxy alloc] init];
    2. theProxy.target = ...;
    3. [theProxy run];

    Das reicht nicht.

    Ein paar Beispiele:

    [object respondsToSelector:@selector( aMethodIntheBackend )] // Unerwartetes Ergebnis

    Original *orignal = … // Verproxyed oder wie man das schreibt
    [original isKindOfClass:[Original class]]; // Unerwartetes Ergebnis
    [original class]; // Liefert Proxyklasse statt Originalklasse

    [orignal release]; // Memory-Leak Du verwendest ARC.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?