Unklarheiten zu Swift

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

  • Na, allgemein ist das schwierig.

    Stell dir vor, du hast eine Liste von Dingen. Die haben alle einen Preis. Nun ist es aber so, dass manche einen Stückpreis haben, andere einen Meterpreis, wieder andere einen Zeitpreis. Wenn du jetzt eine UI baust, um die Werte einzugeben, dann findest du dich schnell mit so etwas ab:

    Quellcode

    1. switch (preisart)
    2. {
    3. case Meterpreis:
    4. unitString = @"€/m";
    5. break;
    6. case Zeitpreis
    7. unitString = @"€/h"
    8. break;
    9. case Stückpreis
    10. unitString = @"€/Stk";
    11. break;
    12. }
    Alles anzeigen


    Solcher Code taucht dann immer wieder auf. Wenn du jetzt einen Flächenpreis einführen möchtest, musst du alle Switch suchen und erweitern. Quer verteilt über den ganzen Code.

    Die Alternative ist eine Klasse zu haben, nennen wir sie Item.Dann hast du Subklassen von Item, die einfach eine Methode unitString haben. Die überschreibst du.

    Schon hast du die Spezialisierungen an einer Stelle gesammelt.
    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"?
  • ...und so richtig schlimm wird Amins Gegenbeispiel, wenn bei den unterschiedlichen Einheitspreisen noch unterschiedliche Berechnungsmodelle mit einfließen. Beim Stückpreis kann es Rabatte, und beim Stundenpreis Nachtzuschläge geben. Die Meterware wird hingegen ab einer gewissen Länge nur noch in rollenweise verkauft, und beim Kilopreis können unterschiedliche Steuersätze zum tragen kommen. Da fallunterscheidest Du Dich tot. ;)
    „Meine Komplikation hatte eine Komplikation.“
  • Das Beispiel ist eigentlich gut, hinkt für mich allerdings ein wenig, da ich den unitString gleich als Attribute in der entsprechenden Klasse mit aufgenommen hätte. Dies mag natürlich daran liegen, dass ich schon länger mit OOD/OOP zu tun habe. Für Einsteiger mag so etwas evtl. nicht direkt ersichtlich sein.

    Andererseits entsteht damit natürlich das Problem, wenn ich das Interface mal lokalisieren möchte, dass ich dann Probleme mit dem Unit String "€/Stk" bekommen. Verwende ich den erhaltenen Unit String dann einfach noch mit NSLocalizedString() oder sollte man lieber gleich einen lokalisierten Unit String aus der entsprechenden Klasse liefern? Die Lokalisierung findet ja eigentlich im UI statt, allerdings muss ich bei Änderungen an der Klasse, dann immer noch das UI, also die Localizable.strings, mit anpassen, obwohl ich dies eigentlich durch das OOD vermeiden wollte.
  • MCDan schrieb:

    Das Beispiel ist eigentlich gut, hinkt für mich allerdings ein wenig, da ich den unitString gleich als Attribute in der entsprechenden Klasse mit aufgenommen hätte. Dies mag natürlich daran liegen, dass ich schon länger mit OOD/OOP zu tun habe. Für Einsteiger mag so etwas evtl. nicht direkt ersichtlich sein.

    Andererseits entsteht damit natürlich das Problem, wenn ich das Interface mal lokalisieren möchte, dass ich dann Probleme mit dem Unit String "€/Stk" bekommen. Verwende ich den erhaltenen Unit String dann einfach noch mit NSLocalizedString() oder sollte man lieber gleich einen lokalisierten Unit String aus der entsprechenden Klasse liefern? Die Lokalisierung findet ja eigentlich im UI statt, allerdings muss ich bei Änderungen an der Klasse, dann immer noch das UI, also die Localizable.strings, mit anpassen, obwohl ich dies eigentlich durch das OOD vermeiden wollte.


    um die lokalisierung soll sich natürlich die klasse kümmern.

    auch würde ich nicht nur die unit zurückgeben sondern gleich eine methode anbieten die den gesamten text zusammenbaut.
    also mit wert drin (kann ja zb kommas enthalten). manchmal wird durch lokalisierung auch die position eines textes geändert. also zb "$ 10" anstatt "10 €"...
  • MCDan schrieb:

    Das Beispiel ist eigentlich gut, hinkt für mich allerdings ein wenig, da ich den unitString gleich als Attribute in der entsprechenden Klasse mit aufgenommen hätte. Dies mag natürlich daran liegen, dass ich schon länger mit OOD/OOP zu tun habe. Für Einsteiger mag so etwas evtl. nicht direkt ersichtlich sein.

    Andererseits entsteht damit natürlich das Problem, wenn ich das Interface mal lokalisieren möchte, dass ich dann Probleme mit dem Unit String "€/Stk" bekommen. Verwende ich den erhaltenen Unit String dann einfach noch mit NSLocalizedString() oder sollte man lieber gleich einen lokalisierten Unit String aus der entsprechenden Klasse liefern? Die Lokalisierung findet ja eigentlich im UI statt, allerdings muss ich bei Änderungen an der Klasse, dann immer noch das UI, also die Localizable.strings, mit anpassen, obwohl ich dies eigentlich durch das OOD vermeiden wollte.
    Das sollte ja nur der Anfang sein. Es gibt noch 16 andere Fälle. Siehe auch macmoonshine. Das geht dann ja schon damit los, dass im Mengeneingabefeld als Einheit "€", "h", … stehen muss.

    Die Lokalisierung würde ich hier wohl außerhalb der Klasse machen, weil etwa € auch außerhalb dieser Anwendung auftauchen wird. Aber das ist sicherlich Tatfrage.
    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"?
  • Amin Negm-Awad schrieb:

    Die Alternative ist eine Klasse zu haben, nennen wir sie Item.Dann hast du Subklassen von Item, die einfach eine Methode unitString haben. Die überschreibst du.

    Schon hast du die Spezialisierungen an einer Stelle gesammelt.
    Und in dieser Methode habe ich dann das einzige Switch und somit brauche ich es nicht mehrmals im Code. Hab ich das richtig verstanden?

    Kommt dieses Denken von Erfahrung oder muss man sich da einmal hinsetzen und wirklich lernen, weil als Anfänger denkt man glaub ich nicht so weit wie ihr.
  • TheFuriousLion schrieb:

    Amin Negm-Awad schrieb:

    Die Alternative ist eine Klasse zu haben, nennen wir sie Item.Dann hast du Subklassen von Item, die einfach eine Methode unitString haben. Die überschreibst du.

    Schon hast du die Spezialisierungen an einer Stelle gesammelt.
    Und in dieser Methode habe ich dann das einzige Switch und somit brauche ich es nicht mehrmals im Code. Hab ich das richtig verstanden?

    Kommt dieses Denken von Erfahrung oder muss man sich da einmal hinsetzen und wirklich lernen, weil als Anfänger denkt man glaub ich nicht so weit wie ihr.


    nein, du hast jeweilige subklassen die das in ihrer entsprechenden methode korrekt handeln.
  • TheFuriousLion schrieb:

    Und in dieser Methode habe ich dann das einzige Switch und somit brauche ich es nicht mehrmals im Code. Hab ich das richtig verstanden?

    Du musst die Fallunterscheidung im Prinzip nur bei der Erzeugung der Objekte machen, wobei dafür in der Regel auch kein Switch notwendig ist. Die Klassen der Objekte wissen dann immer, was das Richtige für das Objekt ist. ;)

    TheFuriousLion schrieb:

    Kommt dieses Denken von Erfahrung oder muss man sich da einmal hinsetzen und wirklich lernen, weil als Anfänger denkt man glaub ich nicht so weit wie ihr.

    Lernen, Lesen, alte Gewohnheiten über den Haufen schmeißen; alles was zu Erfahrung halt dazugehört. ;)
    „Meine Komplikation hatte eine Komplikation.“
  • TheFuriousLion schrieb:

    Amin Negm-Awad schrieb:

    Die Alternative ist eine Klasse zu haben, nennen wir sie Item.Dann hast du Subklassen von Item, die einfach eine Methode unitString haben. Die überschreibst du.

    Schon hast du die Spezialisierungen an einer Stelle gesammelt.
    Und in dieser Methode habe ich dann das einzige Switch und somit brauche ich es nicht mehrmals im Code. Hab ich das richtig verstanden?

    Kommt dieses Denken von Erfahrung oder muss man sich da einmal hinsetzen und wirklich lernen, weil als Anfänger denkt man glaub ich nicht so weit wie ihr.
    Brauchst du ja nicht mehr. Nehmen wir mein einfaches Startbeispiel mit der Einheit:

    Quellcode

    1. @implementation Carpet
    2. - (NSString*)unitString
    3. {
    4. return @"€/m^2";
    5. }
    6. @end

    Quellcode

    1. @implementation Anfahrt
    2. - (NSString*)unitString
    3. {
    4. return @"€";
    5. }
    6. @end


    Wie moonshine und gritsch schon sagen: Die Klasse (allgemeiner: der Typ) ist das switch().

    Man lernt so etwas durch Selbstreflexion und – wichtiger –, indem man nicht fremden Code unbesehen kopiert, sondern vorher durchdenkt.
    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"?
  • TheFuriousLion schrieb:

    Kommt dieses Denken von Erfahrung oder muss man sich da einmal hinsetzen und wirklich lernen, weil als Anfänger denkt man glaub ich nicht so weit wie ihr.

    Von Konfuzius käme jetzt sicher etwas über die "drei Arten, zu lernen" ... ich würde sagen "Lernen durch Schmerzen": Man probiert den Weg, den man kennt und wird dann stutzig, wenn man Code dupliziert. Oder findet tollen Code, den man aber nur dann nutzt, wenn man ihn zu 100% verstanden hat. Oder man fragt in so coolen Foren wie diesem :)

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Danke Amin, jetzt hab ich's endgültig verstanden. Wenn man die zwei Möglichkeiten so betrachtet, ist die Implementierung mit Klassen tatsächlich schöner und besser gelöst. :)

    Mattes, das Forum hier ist wirklich cool. ;)
    Meiner Meinung nach ist es egal, wie man lernt, Hauptsache man lernt.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von TheFuriousLion ()

  • Ich war anfangs richtig überzeugt von Swift, aber umso mehr ich mich mit Swift befasse, desto mehr kann ich die allgemeine Meinung hier im Forum verstehen.

    Gibt es einen logischen Grund, weshalb es in Swift keine einfache Möglichkeit mehr gibt, zu überprüfen, ob ein Objekt in einem Array vorhanden ist? Die einzige Möglichkeit die mir momentan einfällt, ist, den Array zu filtern und zu überprüfen, ob dieser leer ist. Das ist meiner Meinung nach aber kein schöner Code.

    Eine weitere Möglichkeit wäre NSArray zu verwenden. Empfiehlt Apple in Swift eigentlich Array anstatt NSArray zu verwenden?

    Momentan habe ich es so gelöst:

    Quellcode

    1. if someArray({ $0 === someObject }).isEmpty {}

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von TheFuriousLion ()

  • Hey, hey, dein Ansinnen ist aber so etwas von swftical incorrect: Ein Objekt? Welchen Typs denn? Also die Methode müsste ja schon auf AnyType lauten, was sicherlich reichlich Folgeprobleme mit sich bringt. Außerdem wäre statische Typisierung Early-70ies, also so etwas von modern (bitte moddärn ausspechen.)

    Ah, ich hab's! Man macht dafür ein Template, welches eine Methode contains enthält. Jetzt muss aber der Typ irgendwie so etwas wie isEqual implementieren und zwar für jede Empfängerklasse und für jeden Typen. Wird schon nicht richtig elegant. Klingt fast wie 80er und damit nicht mehr so swifty-drifty.

    Damit der Compiler das auseinanderklamüsern kann, muss er jetzt wiederum wissen, dass nur ein Typ verwendet werden kann, der auch isEqual implementiert. Also muss da noch ein Protokoll her.

    Das wäre eine Möglichkeit, geht sicherlich. Die andere ist, man nimmt NSArray und -containsObject, um sich so weit wie möglich von Swift fernzuhalten. Klingt einfacher.

    Das ist ja so ein geiles Gerät, dieses Swift, so ein dreckliges Kind, dass die anderen immer beißt. Suarez als Programmiersprache.
    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"?
  • TheFuriousLion schrieb:

    Gibt es einen logischen Grund, weshalb es in Swift keine einfache Möglichkeit mehr gibt, zu überprüfen, ob ein Objekt in einem Array vorhanden ist? Die einzige Möglichkeit die mir momentan einfällt, ist, den Array zu filtern und zu überprüfen, ob dieser leer ist. Das ist meiner Meinung nach aber kein schöner Code.


    Hier braucht's ja in jedem Fall eine Schleife, die durch das Array läuft und die Zeiger vergleicht. containsObject: in Objective-C macht ja auch nichts anderes.

    Soweit ich das überblicke (ich habe die Beta nicht), wäre die richtige Lösung in Swift wohl ein generisches Protokoll, das eine contains-Methode hinzufügt, die auf Identität vergleicht und nicht nur auf Gleichheit (=== statt nur ==, wofür Equatable reichen würde). An sich würde ich erwarten, daß es das entweder schon gäbe oder es kurzfristig im Standard hinzugefügt würde.

    Bei generischen Deklarationen kann man ja auch noch mit where-clauses Bedingungen angeben, die für den Aufruf-Typ erfüllt sein müssen, damit der Compiler das schon auf der Ebene validieren kann.

    Soweit ich das verstehe, ist es containsObject: in Objective-C einfach egal, ob die Objektklassen zwischen dem Array und dem gesuchten Objekt etwas miteinander zu tun haben. In Swift hat man auf diesem Weg zumindest die Option, schon den Compiler prüfen zu lassen, ob die zusammenpassen (polymorph geht's ja wohl auch, aber dann erklärt man ja auch explizit, daß man keine Prüfung haben will).

    TheFuriousLion schrieb:

    Eine weitere Möglichkeit wäre NSArray zu verwenden. Empfiehlt Apple in Swift eigentlich Array anstatt NSArray zu verwenden?


    Soweit ich das verstehe, sollte man in Swift normalerweise mit Swift-Arrays arbeiten, weil die Implementierung besser optimierbar ist und Swift-spezifische Sprachmechanismen eingesetzt werden können.

    z.B. kann ein Int-Array fester Größe vom Compiler einfach in ein flaches Byte-Array ohne jede dynamische Objektstruktur umgesetzt werden und Methoden-Aufrufe können als Inline-Code komplett aufgelöst werden, wenn die ja auch in Swift mögliche Dynamik tatsächlich nirgends benutzt wird und der Compiler das merkt; Wenn Du ein NSArray deklarierst, hast Du zwangsläufig immer ein dynamisch verwaltetes Runtime-Objekt mit entsprechenden Laufzeit-Kosten bei jedem Methoden-Aufruf.

    Wenn man aus Swift heraus mit Objective-C-APIs hantiert, werden Arrays automatisch zu NSArray "gebridged" – wie das intern genau passiert, kann dann ein Argument dafür oder dagegen sein, das Array auch in Swift schon als NSArray aufzubauen. Wenn das "Bridging" effizient genug passiert (also ohne umständliches Umkopieren der Elemente), würde ich annehmen, daß man bis zum Aufruf mit Swift-Arrays besser bedient wäre, aber das kann natürlich auch von anderen Details abhängen.
  • Amin Negm-Awad schrieb:

    Und das generische Protokoll muss ich dann wie häufig instantieren?


    Wahrscheinlich überhaupt nicht (weil ich davon ausgehe, daß gerade dieser Fall eh schon im Framework bereitgestellt ist oder sehr bald wird), und schon gar nicht zur Laufzeit.

    Wenn man das tatsächlich noch selber nachrüsten müßte (was ich zumindest auf längere Sicht bezweifle) und es nur um diese eine Funktion ginge, würde auch eine generische Funktion reichen. Zumindest funktionell sollte das identisch sein (wenn auch nicht syntaktisch).

    Also etwas in der Art:

    Quellcode

    1. func Contains(here: Array<T>, it: T)->Int? {
    2. (... === ...)
    3. }


    Man könnte also z.B. den Index als optional zurückliefern; Wenn das Objekt nicht enthalten ist, ist der Index nicht definiert, was man auch einfach nur als "nicht enthalten" auswerten kann. Wenn das Objekt aber gefunden wurde, hat man auch gleich den Index.

    Dabei wäre das einzige generische die Deklarations-Syntax, aber nachdem der Compiler abgenickt hat, daß die Klassen der verlinkten Objekte mit der des Vergleichsobjekts zusammenpassen, wäre die generische Funktion zur Laufzeit auch nur noch ein banaler Pointer-Vergleich, der für alle Klassen gemeinsam benutzt werden könnte (und auch gleich noch für alle anderen Datentypen, die zufällig so groß sind wie ein Zeiger). Der Compiler kann sich aufgrund der Typsicherheit sogar dazu entschließen, die Funktion lokal an der Aufrufstelle zu inlinen (mit direktem Vergleich der binären Pointer direkt darin) und alle Calls komplett rauszuwerfen, weil er ja in der Regel genau wissen kann, was womit zu vergleichen ist.

    Es muß also zur Laufzeit weder die Klassenstruktur traversiert noch in einem Protokoll-Deskriptor die Methode gesucht werden und weder die Contains-Methode noch die Vergleichs-Methoden auf den Objekten aufgerufen werden – der Code kann direkt und ohne jeden Aufruf geschlossen generiert werden, mit ggf. nur ein paar wenigen Maschinenbefehlen und ohne einen einzigen Call.

    Es ist gut möglich, daß der Optimizer das im Moment noch nicht ganz soweit packt (zumindest sollten sie beim Kompilieren erst mal die Priorität auf Korrektheit statt auf Geschwindigkeit legen), aber Swift als Sprache ermöglicht genau das.

    Natürlich ist das nur dann ein Vorteil, wenn zur Compile-Zeit auch wirklich genügend Informationen vorhanden sind, aber genau das muß sich der Optimizer ja eben anschauen – wenn wegen der verwendeten unbekannten Objekte tatsächlich mal die volle Dynamik gefordert ist, kann er immer noch automatisch auf entsprechend teurere Lookups und Calls zurückschalten, ohne daß man sich beim Programmieren da größere Gedanken machen müßte.

    Zumindest ist das die Idee dabei. ;)

    Wie gut sie diese Möglichkeiten dann auch wirklich ausnutzen und wie bald, ist natürlich noch die Frage, aber damit können sie ihre Compiler-Mannschaft noch so einige Jahre lang auf Trab halten, und der kompilierte Code sollte sukzessive immer schneller werden.

    Der Haken für Apple ist natürlich, daß Swift den Compiler ähnlich komplex macht wie das bei C++ schon der Fall ist, aber als Programmierer hat man vor allem Vorteile davon, solange der Compiler anständig funktioniert.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von RegExpressive ()