Sue entwirft eine JavaScript - Bibliothek Magician.js
. Sein Dreh- und Angelpunkt ist eine Funktion, die ein Rabbit
aus dem übergebenen Argument zieht .
Sie weiß, dass seine Benutzer ein Kaninchen aus einem String
, einem Number
, einem Function
, vielleicht sogar einem herausziehen möchten HTMLElement
. In diesem Sinne könnte sie ihre API folgendermaßen gestalten:
Die strenge Schnittstelle
Magician.pullRabbitOutOfString = function(str) //...
Magician.pullRabbitOutOfHTMLElement = function(htmlEl) //...
Jede Funktion im obigen Beispiel kann mit dem Argument des im Funktionsnamen / Parameternamen angegebenen Typs umgehen.
Oder sie könnte es so gestalten:
Die "Ad-hoc" -Schnittstelle
Magician.pullRabbit = function(anything) //...
pullRabbit
müsste die Vielfalt der erwarteten Typen berücksichtigen, die das anything
Argument haben könnte, sowie (natürlich) einen unerwarteten Typ:
Magician.pullRabbit = function(anything) {
if (anything === undefined) {
return new Rabbit(); // out of thin air
} else if (isString(anything)) {
// more
} else if (isNumber(anything)) {
// more
}
// etc.
};
Ersteres (strengeres) scheint eindeutiger, vielleicht sicherer und vielleicht leistungsfähiger zu sein - da für die Typprüfung oder die Typkonvertierung nur geringe oder keine zusätzlichen Kosten anfallen. Letzteres (ad hoc) fühlt sich jedoch von außen einfacher an, da es mit jedem Argument "funktioniert", das der API-Konsument für angebracht hält, um es weiterzugeben.
Bei der Beantwortung dieser Frage würde ich mir wünschen, dass Sue bestimmte Vor- und Nachteile für einen der beiden Ansätze (oder für einen anderen Ansatz insgesamt, wenn keiner der beiden ideal ist) kennt und weiß, welchen Ansatz sie beim Entwerfen der API ihrer Bibliothek verfolgen soll.
quelle
Antworten:
Einige Vor- und Nachteile
Vorteile für polymorphe:
Vorteile für Ad-hoc:
Cat
Instanz ziehen? Würde das einfach funktionieren? Wenn nicht, wie ist das Verhalten? Wenn ich den Typ hier nicht einschränke, muss ich das in der Dokumentation oder in den Tests tun, die einen schlechteren Vertrag machen könnten.pullRabbitOutOfString
Version in Motoren wie V8 wahrscheinlich viel schneller ist. Weitere Informationen finden Sie in diesem Video. Edit: Ich habe selbst einen Perf geschrieben, es stellt sich heraus, dass dies in der Praxis nicht immer der Fall ist .Einige alternative Lösungen:
Meiner Meinung nach ist diese Art von Design anfangs nicht sehr "Java-Scripty". JavaScript ist eine andere Sprache mit anderen Redewendungen als C #, Java oder Python. Diese Redewendungen stammen aus Jahren von Entwicklern, die versuchen, die schwachen und starken Teile der Sprache zu verstehen. Ich würde versuchen, mich an diese Redewendungen zu halten.
Ich kann mir zwei schöne Lösungen vorstellen:
Lösung 1: Objekte anheben
Eine gebräuchliche Lösung für dieses Problem besteht darin, Gegenstände mit der Fähigkeit, Kaninchen herausziehen zu lassen, anzuheben.
Das heißt, Sie haben eine Funktion, die eine Art Objekt annimmt und das Herausziehen eines Hutes für dieses hinzufügt. Etwas wie:
Ich kann solche
makePullable
Funktionen für andere Objekte erstellen, ich kann eine erstellenmakePullableString
usw. Ich definiere die Konvertierung für jeden Typ. Nachdem ich meine Objekte angehoben habe, kann ich sie jedoch nicht generisch verwenden. Eine Schnittstelle in JavaScript wird durch eine Enten-Eingabe bestimmt. Wenn es einepullOfHat
Methode gibt, kann ich sie mit der Magician-Methode ziehen.Dann könnte Magier tun:
Das Erhöhen von Objekten mithilfe eines Mix-In-Musters scheint das Richtigere für JS zu sein. (Beachten Sie, dass dies bei Werttypen in der Sprache problematisch ist, bei denen es sich um Zeichenfolgen, Zahlen, Nullen, Undefinierte und Boolesche Werte handelt, die jedoch alle boxfähig sind.)
Hier ist ein Beispiel, wie ein solcher Code aussehen könnte
Lösung 2: Strategiemuster
Als ich diese Frage im JS-Chatroom in StackOverflow diskutierte, schlug mein Freund phenomnomnominal die Verwendung des Strategiemusters vor .
Dies würde es Ihnen ermöglichen, die Fähigkeiten zum Ziehen von Kaninchen aus verschiedenen Objekten zur Laufzeit hinzuzufügen und einen sehr JavaScript-fähigen Code zu erstellen. Ein Zauberer kann lernen, wie er Gegenstände verschiedener Arten aus Hüten zieht, und er zieht sie basierend auf diesem Wissen.
So könnte das in CoffeeScript aussehen:
Sie können den entsprechenden JS-Code hier sehen .
Auf diese Weise profitieren Sie von beiden Welten. Die Aktion des Ziehens ist weder eng mit den Objekten noch mit dem Magier verbunden, und ich denke, dies ist eine sehr schöne Lösung.
Die Verwendung würde ungefähr so aussehen:
quelle
Das Problem ist, dass Sie versuchen, eine Art von Polymorphismus zu implementieren, die in JavaScript nicht vorhanden ist. JavaScript wird im Allgemeinen am besten als ententypisierte Sprache behandelt, obwohl es einige Typenfakultäten unterstützt.
Um die beste API zu erstellen, müssen Sie beide implementieren. Es ist ein bisschen mehr Tipparbeit, spart aber auf lange Sicht viel Arbeit für Benutzer Ihrer API.
pullRabbit
sollte nur eine Arbiter-Methode sein, die Typen prüft und die richtige Funktion aufruft, die diesem Objekttyp zugeordnet ist (zpullRabbitOutOfHtmlElement
. B. ).Auf diese Weise können Benutzer zwar Prototyping verwenden
pullRabbit
, aber wenn sie eine Verlangsamung bemerken, können sie die Typprüfung an ihrem Ende implementieren (wahrscheinlich schneller) und einfachpullRabbitOutOfHtmlElement
direkt anrufen .quelle
Dies ist JavaScript. Wenn Sie es besser machen, werden Sie feststellen, dass es oft einen Mittelweg gibt, der hilft, solche Dilemmata zu überwinden. Außerdem spielt es keine Rolle, ob ein nicht unterstützter 'Typ' von etwas abgefangen wird oder abbricht, wenn jemand versucht, ihn zu verwenden, weil es keine Kompilierung oder Laufzeit gibt. Wenn Sie es falsch verwenden, bricht es. Der Versuch zu verbergen, dass es kaputt gegangen ist oder auf halbem Weg funktioniert, wenn es kaputt gegangen ist, ändert nichts an der Tatsache, dass etwas kaputt gegangen ist.
Nehmen Sie also Ihren Kuchen mit und essen Sie ihn auch und lernen Sie, Typverwechslungen und unnötige Brüche zu vermeiden, indem Sie alles wirklich, wirklich offensichtlich und mit den richtigen Details an den richtigen Stellen aufbewahren.
Zuallererst ermutige ich Sie sehr, sich daran zu gewöhnen, Ihre Enten hintereinander zu bringen, bevor Sie die Typen überprüfen müssen. Am schlanksten und effizientesten (aber nicht immer am besten, wenn es um native Konstruktoren geht) ist es, zuerst die Prototypen zu testen, damit sich Ihre Methode nicht einmal darum kümmern muss, welcher unterstützte Typ im Spiel ist.
Hinweis: Es wird allgemein als schlechte Form angesehen, dies mit Object zu tun, da alles von Object erbt. Ich persönlich würde die Funktion auch meiden. Einige fühlen sich vielleicht ärgerlich, wenn sie den Prototyp eines nativen Konstruktors anfassen, was vielleicht keine schlechte Richtlinie ist, aber das Beispiel kann trotzdem bei der Arbeit mit Ihren eigenen Objektkonstruktoren hilfreich sein.
Ich würde mir keine Sorgen um diesen Ansatz für eine Methode machen, die für eine bestimmte Verwendung geeignet ist und in einer weniger komplizierten App wahrscheinlich nichts aus einer anderen Bibliothek herausholt, aber es ist ein guter Instinkt, zu vermeiden, dass bei systemeigenen Methoden in JavaScript etwas zu allgemein behauptet wird, wenn dies nicht der Fall ist müssen, es sei denn, Sie normalisieren neuere Methoden in veralteten Browsern.
Glücklicherweise können Sie Typen oder Konstruktornamen immer nur vorab auf Methoden abbilden (achten Sie darauf, dass IE <= 8 nicht <object> .constructor.name enthält, sodass Sie es aus den toString-Ergebnissen der Konstruktoreigenschaft analysieren müssen). Sie überprüfen immer noch den Konstruktornamen (typeof ist in JS beim Vergleichen von Objekten irgendwie nutzlos), aber es liest sich zumindest viel besser als eine riesige switch-Anweisung oder if / else-Kette in jedem Aufruf der Methode, die eine breite sein könnte Vielzahl von Objekten.
Oder verwenden Sie denselben Kartenansatz, wenn Sie möchten, dass Sie auf die 'this'-Komponente der verschiedenen Objekttypen zugreifen können, um sie so zu verwenden, als wären sie Methoden, ohne ihre vererbten Prototypen zu berühren:
Ein gutes allgemeines Prinzip in jeder Sprache IMO ist, zu versuchen, Verzweigungsdetails wie diese zu sortieren, bevor Sie zu Code gelangen, der tatsächlich den Auslöser drückt. Auf diese Weise ist es einfach, alle beteiligten Spieler auf dieser obersten API-Ebene zu sehen, um einen guten Überblick zu erhalten, aber es ist auch viel einfacher herauszufinden, wo die Details zu finden sind, die jemanden interessieren könnten.
Hinweis: Dies ist alles ungetestet, da ich annehme, dass niemand eine RL-Verwendung dafür hat. Ich bin mir sicher, dass es Tippfehler gibt.
quelle
Dies ist (für mich) eine interessante und komplizierte Frage, die zu beantworten ist. Eigentlich mag ich diese Frage, also werde ich mein Bestes geben, um sie zu beantworten. Wenn Sie überhaupt nach Standards für die Javascript-Programmierung suchen, werden Sie so viele "richtige" Methoden finden, wie es Leute gibt, die die "richtige" Methode anpreisen.
Aber da suchst du eine Meinung, welcher Weg besser ist. Hier geht nichts.
Ich persönlich würde den "adhoc" -Designansatz vorziehen. Aus einem C ++ / C # -Hintergrund kommend, ist dies eher mein Entwicklungsstil. Sie können eine PullRabbit-Anfrage erstellen und diesen einen Anfragetyp das übergebene Argument überprüfen lassen, um etwas zu tun. Sie müssen sich also keine Gedanken darüber machen, welche Art von Argument zu einem bestimmten Zeitpunkt übergeben wird. Wenn Sie den strikten Ansatz verwenden, müssten Sie immer noch prüfen, welcher Typ die Variable ist. Stattdessen müssen Sie dies tun, bevor Sie den Methodenaufruf ausführen. Am Ende stellt sich die Frage, ob Sie den Typ vor oder nach dem Anruf überprüfen möchten.
Ich hoffe, dies hilft, bitte zögern Sie nicht, weitere Fragen in Bezug auf diese Antwort zu stellen, ich werde mein Bestes tun, um meine Position zu klären.
quelle
Wenn Sie "Magician.pullRabbitOutOfInt" schreiben, wird dokumentiert, worüber Sie beim Schreiben der Methode nachgedacht haben. Der Aufrufer erwartet, dass dies funktioniert, wenn eine Ganzzahl übergeben wird. Wenn Sie schreiben, Magician.pullRabbitOutOfAnything, weiß der Anrufer nicht, was er denken soll, und muss sich in Ihren Code vertiefen und experimentieren. Es könnte für einen Int funktionieren, aber wird es für einen Long funktionieren? Ein Schwimmer? Ein Double? Wenn Sie diesen Code schreiben, wie weit sind Sie bereit zu gehen? Welche Argumente möchten Sie unterstützen?
Mehrdeutigkeit braucht Zeit zum Verstehen. Ich bin nicht einmal davon überzeugt, dass es schneller ist zu schreiben:
Vs:
OK, also habe ich Ihrem Code eine Ausnahme hinzugefügt (die ich sehr empfehle), um dem Anrufer mitzuteilen, dass Sie sich nie vorgestellt haben, dass er Sie überholen würde, was er getan hat. Das Schreiben bestimmter Methoden (ich weiß nicht, ob JavaScript dies zulässt) ist jedoch kein Code mehr und als Aufrufer viel einfacher zu verstehen. Es werden realistische Annahmen darüber getroffen, woran der Autor dieses Codes gedacht hat, und der Code ist einfach zu verwenden.
quelle