Pattern / Architektur Frage

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Pattern / Architektur Frage

    Tach allerseits,

    ich muss endlich mal so ein Design Patterns Buch lesen, nur wann ... ?(

    Konkretes Problem: Ich habe ein Objekt einer Klasse A und möchte zur Laufzeit daraus ein spezialisiertes Objekt der Klasse B machen. B ist direkt von A abgeleitet.

    Das muss doch ein Standardproblem sein. Wie ist der übliche Weg in Objective-C?

    Muss ich NSCopying einbauen und dann in B so etwas wie ein +BFrom:A bauen, worin ich dann ein B Objekt als [copy] des A Objektes erzeuge? Das fühlt sich alles irgendwie falsch an. :(

    Danke!
  • Ich versteh' Dein konkretes Problem nicht, das hört sich ungewöhnlich an. Also Du hast die Klasse "Fahrzeug" und möchtest daraus ein Objekt vom Typ "Auto" machen, wobei "Auto" von "Fahrzeug" erbt. Richtig?

    Zwei Möglichkeiten:

    1. Du willst wirklich ein neues Auto-Objekt aus dem Fahrzeug machen? Wie soll das gehen, die Spezialisierung der Klasse "Fahrzeug", nämlich "Auto" hat ja sicherlich noch mehr Attribute. Z.b. den Eltern/Kind-Parkplatzverstopfungsfaktor (Achtung, Klischee! Besonders hoch bei SUVs oder bei süddeutschen Automarken). Wie willst Du die füllen? Aber egal: Solltest Du das meinen: Einfach einen Konstruktor in der Klasse "Auto" bauen, der als Argument ein "Fahrzeug" bekommt.

    2. Am Ende möchtest Du nur casten? Also Du bekommst ein "Fahrzeug"-Objekt und vermutest, es könnte ein "Auto" sein? Wenn das der Fall ist, auf Klasse testen und dann casten:

    Quellcode

    1. Fahrzeug *f = irgendwoherFaehrtEinFahrzeugDaher();
    2. if( [f isKindOfClass:[Auto class]] )
    3. {
    4. Auto *a = (Auto*)f;
    5. }


    Wobei man bei solchen Konstrukten nochmal überlegen sollte, ob es sich nicht anders lösen lässt. Schön ist anders...

    Gruß

    gandhi
  • Du kannst nicht einfach aus Objekt vom Typ A ein Objekt vom Typ B machen.

    Auch wenn 'Rabe' (B) definitiv ein 'Vogel' (A) ist, ist es dennoch nicht ohne Weiteres möglich, aus jedem 'Vogel' einen 'Rabe'n zu machen.
    Schließlich sind nicht alle 'Vögel' Singvögel, der Rabe hingegen schon.

    Wenn Du also zwingend ein Objekt vom Typ 'Rabe' brauchst, dann erzeugst Du Dir zur Laufzeit ein Objekt vom Typ 'Rabe'.
    'Rabe' kannst Du auch ganz problemlos als 'Vogel' verwenden – nur umgekehrt geht das halt nicht.

    Natürlich kannst Du in 'Rabe' implementieren: +(instancetype)rabeFromVogel:(Vogel*)bird und in dann entsprechend die Properties setzen. Nur hast Du dann ja kein spezialisiertes Objekt von 'Vogel' gemacht, sondern ein generelles Objekt 'Vogel' als Vorlage für ein spezialisiertes Objekt 'Rabe' genommen.
    Mag für Properties wie -(bool)hatSchnabel und -(bool)kannFliegen sinnvoll sein, kann man sich aber drüber streiten.

    Dein konkreter Fall klingt nach nichts Konkretem.
    Eventuell solltest Du den konkreten Fall etwas konkretisieren. :P

    Und zwar nicht nach dem 'Ich will die Klasse bla aus Klasse blubb haben'. (Also nicht den Lösungsweg beschreiben, den Du gewählt hast.)
    Sondern 'Der User soll zunächst einen allgemeinen Bankaccount einrichten und ihn später in etwas Spezielleres wie ein Girokonto umwandeln können.'

    Der Hinweis auf die Laufzeit ist übrigens überflüssig. Zur Compilezeit wird da nicht viel erzeugt. ;)

    //Nachtrag_
    Natürlich geht das. Ich weiß, dass das geht. Mit der Objective-C Runtime geht so Einiges. Instanz 'Vogel' nehmen, verknüpfte Klasse 'Vogel' weghauen, Klasse 'Rabe' verknüpfen, Instanz 'Vogel' aktualisieren… Das ist hier aber sicherlich nicht gewollt.
    «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
  • Zunächst einmal fühlt sich das ziemlich falsch an, weil die gewollte Lösung (Typänderung zur Laufzeit) ziemlich falsch ist. Aber es gibt solche Situationen, ich weiß.

    Leicht ist es, ein neues Objekt der Klasse B aus einem Objekt der Klasse A herzustellen. Nur reicht das nicht immer, da das Ursprungsobjekt möglicherweise schon zahlreich referenziert ist und man nicht weiß, wo überall. (Und das zu wissen, wäre das Gegenteil eines Pattern.)

    Wenn es also wirklich so ist, dass du den Typen des Objektes verändern musst, so kann man im Wesentlichen zwei Fälle unterscheiden: B hat keine zusätzlichen Propertys oder doch. Wenn es keine zusätzlichen hat, dann ist das ein 3-Zeiler. Wenn es zusätzliche hat, dann liegt das Problem darin, den zusätzlichen Speicher zu beschaffen. Man löst das mit Proxys.

    Bevor ich hier aber schon wieder Lösungen hinschreibe, die man in Swift natürlich nicht formulieren kann, sag doch ml deinen Anwendungsfall und ob B zusätzliche Propertys hat.
    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"?
  • Ja, also Danke Jungs. Gestern Abend im Bad ist mir die richtige Lösung auch eingefallen.

    Ich erzeuge das Objekt am Anfang schon gleich mit der richtigen Klasse ("Rabe" statt "Vogel", "Auto" statt "Fahrzeug"). Mein ganzer Ansatz war wie schon bemerkt falsch. Und in meinem Fall funktioniert die richtige Lösung auch tatsächlich.

    Es geht konkret um Netzwerkgeräte. Anhand einer bestimmten Eigenschaft (sagen wir NSString *type) kann ich spezielle Netzwerkgeräte erkennen und dann ein Objekt einer darauf spezialisierten Klasse erstellen. Und ja, B hat dann weitere Properties.

    Am Anfang mache ich mir also ein Dictionary mit Type-Strings als Keys und dem dazu gehörigen Klassenobjekt als Wert. Wenn ich ein Netzwerkgerät finde, schaue ich in dem Dictionary nach, ob ich für diesen Typstring eine spezielle Klasse zur Verfügung habe (z.B. Drucker) und dann erstelle ich das Objekt mit der Klasse aus dem Dictionary. Bei unbekanntem Typ wird dann halt nur ein Gerät Objekt erstellt.

    Ich wollte halt später aus dem Gerät einen Drucker machen. Jetzt ist es quasi umgekehrt. Ein Drucker ist immer auch als Drucker erstellt, dann intern weiter als Gerät weiter behandelt, bis der Code an der Stelle ist, wo ich etwas spezielles mit Druckern machen möchte und da schaue ich dann mit isKindOfClass, ob das Gerät tatsächlich ein Drucker ist und bin dann auf der sicheren Seite.

    In echt sind es keine Drucker, dient hier nur als Beispiel.

    Danke! Manchmal muss man nur mit jemandem reden ...
  • Aber wenn ich Methoden aufrufen möchte, die es nur in der Drucker Klasse gibt, ist doch nichts zum Überschreiben aus der Basisklasse da.

    Also klar, ich könnte auch respondsToSelector statt isKindOfClass nehmen, aber das ist doch Jacke wie Hose.

    Aber ich glaube ich habe noch nicht verstanden, was Du eigentlich meinst. ;)
  • Du kannst auch in der gemeinsamen Oberklasse Methoden mit einer leeren Implementierung dafür vorsehen. Die Methoden müssen ja keine detaillierten Abfragen z. B. Füllstand abfragen) darstellen, sondern können stattdessen allgemeinere Anwendungsfälle (z. B. Funktionsprüfung des Gerätes) darstellen. Anstatt eine Methode mit spezialisierten Anwendungsfällen für unterschiedliche Gerätetypen aufzublähen, solltest Du die Spezialisierung den speziellen Klassen überlassen. Dafür sind sie ja da.

    Schau Dir mal das Entwurfsmuster Schablonenmethode an.
    „Meine Komplikation hatte eine Komplikation.“
  • Nu lasst ihm doch erst mal den Triumph, bevor ihr hier mit weiteren Pattern um euch schmeißt. =)

    (In einem ähnlichen Fall habe ich für so etwas auch den Factory-Ansatz gewählt.)
    «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
  • Also, Danke dass ihr euch so liebevoll darum kümmert. :thumbsup:

    In der Tat habe ich bereits eine +gerätFürTyp: Methode, die eben genau auf das Dictionary mit dem Klassen zurück greift und so gleich das richtige Objekt erzeugt. Also genau so wie gritsch es vorschlägt.

    Ich kann mir nicht vorstellen, dass ihr mich tatsächlich davon überzeugen wollt, in der Geräte Klasse eine leere druckDiesesPDF: Methode zu implementieren, nur weil es in einer speziellen abgeleiteten Klasse dafür eine sinnvolle Anwendung gibt. Was soll druckDiesesPDF: in einem Fernseher oder Kühlschrank Objekt? ?(

    Ich hatte nicht vor, irgendeine Methode mit spezialisierten Fällen aufzublähen. Um im fiktiven Beispiel zu bleiben: Ich benutze nur mein "Geräte-Framework" in einer Drucker-App und bin einfach in dieser App nicht an Fernsehern und Kühlschränken interessiert. Also habe ich tatsächlich eine Stelle, an der ich die Drucker vom Rest unterscheiden muss. Das mache ich dann also jetzt mit isKindOfClass:[Drucker class] - ist das verkehrt? Geht das besser?
  • Verkehrt ist vielleicht das falsche Wort. Du machst eine explizite Unterscheidung anhand der Klasse und das führt schnell zu Stinkern (Maus und Monitor fordern ja auch Ihre Rechte ein ;-). Dadurch gelangt man dann schnell zu Fallunterscheidungsketten, die sich vielleicht auch noch mehrfach im Code wiederholen (puh, mach mal einer das Fenster auf). Da ist Abfrage nach Methoden über respondsToSelector: schon besser. Wenn eine weitere Klasse die gleiche Eigenschaft besitzt, brauchst du den aufrufenden Code dafür nicht zu ändern.

    BTW: Eine Methode druckeDiesesPDF: finde ich hier zu kleinteilig gedacht. Das ist klar, dass das nur ein Drucker versteht. Wenn Du die Methode verarbeiteDiesesDokument: nennst, macht sie vielleicht auch auf anderen Geräten Sinn.
    „Meine Komplikation hatte eine Komplikation.“
  • Eben. -verarbeiteDiesesDokument: kann ja im Drucker intern einfach -druckeDiesesPDF: aufrufen.
    Im OCR Reader kann es -liesAusDiesemDokument: aufrufen. Beim Fax lässt sich -sendeDiesesDokument: implementieren.

    Und im Schredder dann -häcksleDiesenWisch:

    Klar, ein Fernseher braucht keine Dokumente.

    Wenn Du jetzt aber Multifunktionsgerät hast und nur auf 'Drucker' abfragst, dann druckt Dein Multifunktionsgerät nicht. Scannen kann es vermutlich auch nicht, wenn Du nur auf 'Scanner' abfragst.

    Unter C++ würde man jetzt einfach sagen: Hey, Mehrfachvererbung! Multifunktionsgerät erbt von 'Drucker' und von 'Scanner' und alles ist tutti! Ach, komm, von 'Kopierer' erbt es auch noch. Heissa!!! \o/

    Unter Objective-C sagt man eher: "Kannst Du drucken? Gut, dann druck." "Kannst Du scannen? Gut, dann scan." "Kannst Du kopieren? Fein, dann kopier."

    Wenn also irgend ein Freak aus dem Chaos Computer Club Deinen Toaster so umgebaut hat, dass er Schwarzweißdokumente auf Deine Toastscheiben kohlen kann, dann druckt hier auch Dein Toaster.

    C++: Wer bist Du? Aha, dann kannst Du das auch…
    Objective-C: Kannste das?
    «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
  • Ah, ich verstehe.

    Aber das ist in meinem Anwendungsfall tatsächlich nicht sinnvoll. Die einzelnen Geräte basieren auf jeweils einzelnen expliziten Spezifikationen. Das denke ich mir nicht aus. Insofern ist es vorgegeben und fühlt sich für mich auch 100% richtig an, dass die druckDiesesPDF: Methode nur in der Drucker Klasse implementiert wird und nicht in der Geräte Klasse.

    Aber ich habe verstanden was ihr meint. Danke!

    Abschließende Frage: welches Buch muss ich mir kaufen? Irgendwas mit Design Patterns speziell in der Cocoa (Touch) Welt scheint zielführend zu sein. Habt ihr da was im eigenen Regal, was ihr empfehlen könnt?
  • smk schrieb:

    Irgendwas mit Design Patterns speziell in der Cocoa (Touch) Welt scheint zielführend zu sein. Habt ihr da was im eigenen Regal, was ihr empfehlen könnt?


    Meiner Meinung tut da der Blick über den Tellerrand bzw. ein höheres Abstraktionsniveau gut. Ich würd's also nicht an Cocoa/Objective-C festmachen. Ein Klassiker ist sicherlich das GoF-Buch.

    ciao

    gandhi
  • Da ist nur verwirrend, dass der Kram in Cocoa irgendwie anders heißt als die GoF es genannt hat.
    Ansonsten lässt sich auch Head First Design Patterns empfehlen – auch auf Deutsch.
    «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
  • Ich will ja kein weiteres Fass aufmachen, aber ich finde sowohl Vererbung als auch jede Menge leerer Methoden doof. Vererbung wird im Allgemeinen überbewertet. Ewig lange Interfaces (im Sinne von "druckeDokument", "sendeDokument" usw) sind Mist und bringen strukturelles Detailwissen in Bereiche, wo sie nicht hingehören. Und etwas wie "verarbeiteDokument"? Nee, nicht wirklich, oder? Da ist ja gar keine Semantik mehr drin. Und was tut ein Gerät, das mehrere Sachen mit Dokumenten anstellen kann?

    Ich würde es nicht als Drucker sehen, sondern als Gerät, das einen Druckdienst anbietet. Der Druckdienst ist ein separates Interface und kann entweder vom Gerät selbst oder von einem separaten Objekt implementiert werden. Das Gerät selbst bietet aber nur eine Möglichkeit, nach einem bestimmten Dienst zu fragen und gibt dann eins zurück, wenn es das kann. So kann man die Dienste auch lazy erzeugen - halt erst dann, wenn danach gefragt wird. dafür gibt's bestimmt auch ein Pattern mit einem schönen Namen, aber die verwechsele ich eh immer.
    Multigrad - 360°-Produktfotografie für den Mac
  • Ja, da hatte ich auch schon dran gedacht: Die Geräte sollten (als Modellobjekte) natürlich auch keine Controller-Logik enthalten. Fand ich aber für den Anfang für den OP ein bisschen viel. isKindOfClass:-Fallunterscheidungen gewöhnt man sich leicht an, und wird sie schwer wieder los. ;)
    „Meine Komplikation hatte eine Komplikation.“