Anti-was? Es klingt wahrscheinlich viel komplizierter als es ist. In den letzten Jahrzehnten konnten Programmierer eine nützliche Auswahl von „Entwurfsmustern“ finden, die häufig in ihren Codelösungen vorkommen. Bei der Lösung ähnlicher Probleme konnten sie Lösungen "klassifizieren", die sie daran hinderten, das Rad ständig neu zu erfinden. Es ist wichtig zu wissen, dass diese Muster eher als Entdeckungen betrachtet werden sollten als die Erfindungen einer Gruppe fortgeschrittener Entwickler.
Wenn dies für Sie eher neu ist und Sie sich mehr auf der Anfängerseite von Ruby / Rails sehen, dann ist dies genau für Sie geschrieben. Ich denke, es ist am besten, wenn Sie es als einen schnellen Einstieg in ein viel tieferes Thema betrachten, dessen Meisterschaft nicht über Nacht stattfinden wird. Trotzdem bin ich fest davon überzeugt, dass der Einstieg in diese frühen Anfänger und ihre Mentoren enorm von Nutzen sein werden.
AntiPatterns - wie der Name schon sagt - stellen so ziemlich das Gegenteil von Mustern dar. Sie sind Entdeckungen von Lösungen für Probleme, die Sie auf jeden Fall vermeiden sollten. Sie repräsentieren oft die Arbeit von unerfahrenen Programmierern, die noch nicht wissen, was sie noch nicht wissen. Schlimmer noch, sie könnten das Ergebnis einer faulen Person sein, die nur aus guten Gründen Best Practices und Tools-Frameworks ignoriert - oder sie glauben, sie brauchen sie nicht. Was sie anfangs durch die Erledigung schneller, fauler oder schmutziger Lösungen an Zeitersparnis gewinnen könnten, wird sie später oder im Verlauf des Projektlebens als traurigen Nachfolger verfolgen.
Unterschätzen Sie nicht die Auswirkungen dieser schlechten Entscheidungen - sie werden Sie plagen, egal was passiert.
Ich bin mir sicher, dass Sie gehört haben, wie die "Fat models, skinny controller" unzählige Male gesungen haben, als Sie mit Rails anfingen. OK, jetzt vergiss das! Sicher, die Geschäftslogik muss in der Modellschicht gelöst werden, aber Sie sollten nicht geneigt sein, alles sinnlos in das Unternehmen zu packen, nur um zu vermeiden, dass die Grenzen in das Gebiet des Controllers gelangen.
Hier ist ein neues Ziel, das Sie anvisieren sollten: „Skinny-Modelle, Skinny-Controller“. Sie fragen sich vielleicht: „Nun, wie sollten wir den Code so anordnen, dass dies erreicht wird? Schließlich ist es ein Nullsummenspiel.“ Der Name des Spiels ist Komposition, und Ruby ist gut gerüstet, um Ihnen viele Optionen zur Vermeidung von Fettleibigkeit zu geben.
Bei den meisten (Rails) Webanwendungen, die datenbankgestützt sind, konzentriert sich der Großteil Ihrer Aufmerksamkeit und Arbeit auf die Modellebene - vorausgesetzt, Sie arbeiten mit kompetenten Designern zusammen, die in der Lage sind, ihre eigenen Sachen in der Ansicht zu implementieren, meine ich. Ihre Modelle werden von Natur aus mehr „Schwerkraft“ haben und komplexer werden.
Die Frage ist nur, wie Sie diese Komplexität bewältigen wollen. Mit Active Record haben Sie definitiv genug Seil zum Aufhängen und machen Ihr Leben unglaublich einfach. Es ist ein verlockender Ansatz, um Ihre Modellebene zu entwerfen, indem Sie einfach dem Pfad des höchsten unmittelbaren Komforts folgen. Bei einer zukunftssicheren Architektur wird jedoch viel mehr Wert auf das Betrachten großer Klassen gelegt und alles in Active Record-Objekte gesteckt.
Das eigentliche Problem, mit dem Sie hier umgehen, ist Komplexität - unnötig, würde ich sagen. Klassen, die Tonnen von Code anhäufen, werden allein durch ihre Größe komplex. Sie sind schwieriger zu pflegen, schwer zu analysieren und zu verstehen, und es wird zunehmend schwieriger, sie zu ändern, da ihrer Zusammensetzung wahrscheinlich eine Entkopplung fehlt. Diese Modelle übertreffen oft ihre empfohlene Fähigkeit, eine einzige Verantwortung zu bewältigen, und sind eher überall vorhanden. Im schlimmsten Fall werden sie wie Müllwagen, die mit dem ganzen Müll umgehen, der faul auf sie geworfen wird.
Wir können es besser machen! Wenn Sie der Meinung sind, dass Komplexität keine große Sache ist - schließlich sind Sie besonders, klug und denken wieder alles! Komplexität ist der berüchtigtste Serienprojektkiller, der nicht in der freundlichen Nachbarschaft "Dark Defender" ist..
"Skinnier-Modelle" erreichen eine Sache, die Fortgeschrittene im Codier-Geschäft (wahrscheinlich viel mehr Berufe als Code und Design) zu schätzen wissen, und was wir alle auf Einfachheit anstreben sollten! Oder zumindest mehr davon, was ein guter Kompromiss ist, wenn die Komplexität schwer zu überwinden ist.
Welche Werkzeuge bietet Ruby, um unser Leben in dieser Hinsicht zu erleichtern und uns das Fett unserer Modelle abschneiden zu lassen? Einfach, andere Klassen und Module. Sie identifizieren kohärenten Code, den Sie in ein anderes Objekt extrahieren könnten, und erstellt so eine Modellschicht, die aus Agenten mit angemessener Größe besteht, die ihre eigenen, eindeutigen Verantwortlichkeiten haben.
Denken Sie an einen talentierten Performer. Im wirklichen Leben kann eine solche Person in der Lage sein zu rappen, zu brechen, Texte zu schreiben und ihre eigenen Lieder zu produzieren. Beim Programmieren bevorzugen Sie die Dynamik einer Band - hier mit mindestens vier verschiedenen Mitgliedern -, bei der jede Person für so wenige Dinge wie möglich verantwortlich ist. Sie möchten ein Klassenorchester aufbauen, das mit der Komplexität des Komponisten zurechtkommt - keine mikromanagende geniale Maestro-Klasse aller Berufe.
Schauen wir uns ein Beispiel eines fetten Modells an und spielen Sie mit ein paar Optionen, um mit der Fettleibigkeit umzugehen. Das Beispiel ist natürlich ein Dummy, und wenn ich diese doofe kleine Geschichte erzähle, hoffe ich, dass es für Neulinge leichter zu verdauen und zu folgen ist.
Wir haben eine Spectre-Klasse, die zu viele Verantwortlichkeiten hat und daher unnötig gewachsen ist. Abgesehen von diesen Methoden kann ich mir leicht vorstellen, dass ein solches Exemplar bereits viele andere Dinge angesammelt hat, die durch die drei kleinen Punkte gut repräsentiert werden. Specter ist auf dem besten Weg, eine Gottesklasse zu werden. (Die Chancen sind groß, einen solchen Satz in absehbarer Zeit wieder sinnvoll zu formulieren!)
"Ruby Klasse Specter < ActiveRecord::Base has_many :spectre_members has_many :spectre_agents has_many :enemy_agents has_many :operations
…
def turn_mi6_agent (enemy_agent) setzt "MI6 agent # enemy_agent.name" auf Spectre um
def turn_cia_agent (enemy_agent) setzt "CIA-Agent # enemy_agent.name" auf Spectre um
def turn_mossad_agent (enemy_agent) setzt "Mossad Agent # enemy_agent.name" auf Spectre um
def kill_double_o_seven (spectre_agent) spectre_agent.kill_james_bond end
def dispose_of_cabinet_member (number) spectre_member = SpectreMember.find_by_id (number)
"Ein gewisser Täter ist an der absoluten Integrität dieser Bruderschaft gescheitert. Die geeignete Handlung ist, in seinem Stuhl die Nummer # Nummer zu rauchen. Seine Dienste werden nicht sehr vermisst werden" spectre_member.die ende
def print_assignment (operation) setzt das Ziel "Operation # operation.name" auf # operation.objective. "ende
Privatgelände
def enemy_agent #clever code end
def spectre_agent #clever code end
def operation #clever code end
…
Ende
"
Specter wendet verschiedene Arten von feindlichen Agenten an, Delegierte töten 007, grillen Spectre-Kabinettsmitglieder an, wenn sie versagen, und druckt auch operative Aufgaben aus. Ein klarer Fall von Mikromanagement und definitiv ein Verstoß gegen das „Prinzip der Einzelverantwortung“. Auch private Methoden stapeln sich schnell.
Diese Klasse muss nicht das meiste von dem wissen, was gerade in ihr ist. Wir werden diese Funktionalität in einige Klassen aufteilen und sehen, ob die Komplexität einiger mehr Klassen / Objekte die Fettabsaugung wert ist.
"Rubin
Klasse Specter < ActiveRecord::Base has_many :spectre_members has_many :spectre_agents has_many :enemy_agents has_many :operations
…
def turn_enemy_agent Interrogator.new (enemy_agent) .turn Ende
Privatgelände
def enemy_agent self.enemy_agents.last end end
Klasse Interrogator attr_reader: enemy_agent
def initialize (enemy_agent) @enemy_agent = enemy_agent end
def wiederum feind_agent.turn ende ende
Klasse EnemyAgent < ActiveRecord::Base belongs_to :spectre belongs_to :agency
def turn setzt "Nach ausgiebiger Gehirnwäsche, Folter und Bargeldhaufen ..." Ende
Klasse MI6Agent < EnemyAgent def turn super puts “MI6 agent #name turned over to Spectre” end end
Klasse CiaAgent < EnemyAgent def turn super puts “CIA agent #name turned over to Spectre” end end
Klasse MossadAgent < EnemyAgent def turn super puts “Mossad agent #name turned over to Spectre” end end
Klasse NumberOne < ActiveRecord::Base def dispose_of_cabinet_member(number) spectre_member = SpectreMember.find_by_id(number)
"Ein gewisser Täter ist an der absoluten Integrität dieser Bruderschaft gescheitert. Die geeignete Handlung ist, die Nummer # Nummer in seinem Stuhl zu rauchen. Seine Dienste werden nicht zu kurz kommen."
Klasse Operation < ActiveRecord::Base has_many :spectre_agents belongs_to :spectre
def print_assignment legt fest, dass "Ziel # Name 'Ziel ist, # Ziel." end end
Klasse SpectreAgent < ActiveRecord::Base belongs_to :operation belongs_to :spectre
def kill_james_bond setzt “Mr. Bond, ich erwarte, dass du stirbst! “Ende Ende
Klasse SpectreMember < ActiveRecord::Base belongs_to :spectre
„Nein, nein, es war nicht meeeeeeeee! ZCHUNK! “Ende Ende
"
Ich denke, der wichtigste Teil, den Sie beachten sollten, ist, wie wir eine reine Ruby-Klasse verwenden Interrogator
die Abwendung von Agenten verschiedener Agenturen zu bewältigen. Beispiele aus der Praxis könnten einen Konverter darstellen, der beispielsweise ein HTML-Dokument in ein PDF-Dokument und umgekehrt umwandelt. Wenn Sie nicht die volle Funktionalität von Active Record-Klassen benötigen, warum sollten Sie diese verwenden, wenn eine einfache Ruby-Klasse auch den Trick beherrscht? Ein bisschen weniger Seil zum Aufhängen.
Die Spectre-Klasse überlässt es dem unangenehmen Geschäft, Agenten an die Interrogator
Klasse und nur Delegierte dazu. Dieser hat nun die einzige Verantwortung, gefangene Agenten zu foltern und einer Gehirnwäsche zu unterziehen.
So weit, ist es gut. Aber warum haben wir für jeden Agenten separate Klassen erstellt? Einfach. Anstatt einfach die verschiedenen Drehmethoden direkt zu extrahieren turn_mi6_agent
nach Interrogator
, Wir haben ihnen ein besseres Zuhause in ihrer jeweiligen Klasse gegeben.
Als Ergebnis können wir Polymorphismus effektiv nutzen und sich nicht um Einzelfälle für Turning Agents kümmern. Wir sagen nur diesen verschiedenen Agentenobjekten, dass sie sich wenden sollen, und jeder von ihnen weiß, was zu tun ist. Das Interrogator
Sie müssen nicht genau wissen, wie sich jeder Agent ändert.
Da alle diese Agenten Active Record-Objekte sind, haben wir ein generisches erstellt, EnemyAgent
, Das hat ein allgemeines Gefühl dafür, was ein Agent bedeutet, und wir kapseln dieses Bit für alle Agenten an einem Ort ein, indem wir es unterteilen. Wir nutzen diese Vererbung, indem wir die Wende
Methoden der verschiedenen Agenten mit Super
, Daher erhalten wir ohne Doppelarbeit Zugang zum Gehirnwäsche- und Foltergeschäft. Einzelne Verantwortlichkeiten und keine Vervielfältigung sind ein guter Ausgangspunkt, um weiterzumachen.
Die anderen Active Record-Klassen übernehmen verschiedene Verantwortlichkeiten, um die sich Specter nicht kümmern muss. Bei „Nummer Eins“ wird das Grillen von gescheiterten Specter-Kabinettmitgliedern normalerweise selbst gegrillt. Auf der anderen Seite wissen versagende Specter-Mitglieder, wie sie selbst sterben, wenn sie auf ihrem Stuhl von geraucht werden Nummer Eins
. Operation
Jetzt druckt er seine Aufgaben selbst und braucht die Zeit von Spectre nicht mit solchen Erdnüssen zu verschwenden.
Zu guter Letzt wird James Bond in der Regel von einem Agenten auf dem Feld getötet kill_james_bond
ist jetzt eine Methode auf SpectreAgent
. Goldfinger hätte das anders gehandhabt, natürlich muss ich mit diesem Laser-Ding spielen, wenn Sie eines haben, denke ich.
Wie Sie deutlich sehen können, haben wir jetzt zehn Klassen, in denen wir bisher nur eine hatten. Ist das nicht zu viel? Es kann sicher sein. Es ist ein Problem, mit dem Sie meistens kämpfen müssen, wenn Sie diese Verantwortlichkeiten aufteilen. Sie können dies definitiv übertreiben. Aber aus einem anderen Blickwinkel zu betrachten, könnte helfen:
Ich behaupte nicht, dass diese Fragen jedes Mal von Ihrer Liste entfernt werden müssen, aber dies sind die Dinge, die Sie wahrscheinlich anfangen sollten, sich selbst zu fragen, während Sie Ihre Modelle abnehmen.
Das Entwerfen von Skinny-Modellen kann schwierig sein, ist jedoch eine wesentliche Maßnahme, um Ihre Anwendungen gesund und agil zu halten. Dies sind auch nicht die einzigen konstruktiven Wege, um mit fetten Modellen umzugehen, aber sie sind vor allem für Anfänger ein guter Anfang.
Dies ist wahrscheinlich das offensichtlichste AntiPattern. Wenn Sie von einer testgetriebenen Seite kommen, kann das Berühren einer ausgereiften App ohne Testabdeckung eine der schmerzhaftesten Erfahrungen sein. Wenn Sie die Welt und Ihren eigenen Beruf mehr als alles andere hassen möchten, verbringen Sie einfach sechs Monate mit einem solchen Projekt, und Sie erfahren, wie viel von einem Misanthrop in Ihnen steckt. Ein Scherz natürlich, aber ich bezweifle, dass es Sie glücklicher machen wird und dass Sie es immer wieder tun möchten. Vielleicht reicht eine Woche auch aus. Ich bin mir ziemlich sicher, dass Ihnen das Wort Folter öfter in den Sinn kommt als Sie denken.
Wenn das Testen bisher noch nicht Teil Ihres Prozesses war und diese Art von Schmerz für Ihre Arbeit normal erscheint, sollten Sie vielleicht bedenken, dass das Testen nicht so schlimm ist, noch ist es Ihr Feind. Wenn Ihre Freude am Code mehr oder weniger konstant über null liegt und Sie Ihren Code furchtlos ändern können, ist die Gesamtqualität Ihrer Arbeit viel höher als bei einem von Angst und Leiden befallenen Output.
Überschätze ich? Das glaube ich wirklich nicht! Sie möchten eine sehr umfangreiche Testabdeckung haben, nicht nur, weil es ein großartiges Entwurfstool ist, um nur Code zu schreiben, den Sie tatsächlich benötigen, sondern auch, weil Sie Ihren Code irgendwann in der Zukunft ändern müssen. Sie sind viel besser in der Lage, mit Ihrer Codebase zu interagieren - und viel selbstbewusster -, wenn Sie einen Testgurt haben, der Refactorings, Wartung und Erweiterungen unterstützt und führt. Sie werden sicher auf der Straße auftreten, daran zweifeln.
Dies ist auch der Punkt, an dem sich eine Testsuite für die zweite Dividendenrunde auszahlt, denn die erhöhte Geschwindigkeit, mit der Sie diese Qualitätsänderungen sicher vornehmen können, kann nicht durch Apps erreicht werden, die von Leuten geschrieben werden, die Tests schreiben ist Unsinn oder braucht zu viel Zeit.
Dies sind Modelle, die sehr neugierig sind und zu viele Informationen über andere Objekte oder Modelle sammeln möchten. Dies steht im krassen Gegensatz zu einer der grundlegendsten Ideen in der objektorientierten Programmier-Kapselung. Vielmehr wollen wir in sich geschlossene Klassen und Modelle anstreben, die ihre inneren Angelegenheiten so weit wie möglich selbst steuern. In Bezug auf Programmierkonzepte verstoßen diese voyeuristischen Modelle grundsätzlich gegen das "Prinzip des geringsten Wissens", auch bekannt als "Gesetz des Demeters" - wie auch immer Sie es aussprechen wollen.
Warum ist das ein Problem? Es ist eine Form der Vervielfältigung - eine subtile - und führt auch zu Code, der viel spröder als erwartet ist.
Das Gesetz von Demeter ist so ziemlich der zuverlässigste Code-Geruch, den Sie immer angreifen können, ohne sich Sorgen über mögliche Nachteile zu machen.
Ich nehme an, es als "Gesetz" zu bezeichnen, war nicht so anmaßend, wie es auf den ersten Blick klingen mag. Betrachte diesen Geruch, denn du wirst ihn in deinen Projekten sehr brauchen. Im Wesentlichen heißt es, dass Sie in Bezug auf Objekte Methoden für den Freund Ihres Objekts aufrufen können, nicht jedoch für den Freund Ihres Freundes.
Dies ist ein allgemeiner Weg, um es zu erklären, und es läuft alles darauf hinaus, nicht mehr als einen Punkt für Ihre Methodenaufrufe zu verwenden. Übrigens ist es vollkommen in Ordnung, mehr Punkte oder Methodenaufrufe zu verwenden, wenn Sie mit einem einzelnen Objekt arbeiten, das nicht weiter versucht. So etwas wie @ arms.find_by_name ('Giftpfeil'). formel
ist gut so. Sucher können manchmal einige Punkte sammeln. Sie in dedizierte Methoden einzukapseln, ist dennoch eine gute Idee.
Schauen wir uns ein paar schlechte Beispiele aus den oben genannten Klassen an:
"Rubin
@ operation.spectre_agents.first.kill_james_bond
@ spectre.operations.last.spectre_agents.first.name
@ spectre.enemy_agents.last.agency.name
"
Um den Dreh raus zu bekommen, hier ein paar weitere fiktive:
"Rubin
@ quartermaster.gizmos.non_lethal.favorite
@ mi6.operation.agent.favorite_weapon
@ mission.agent.name
"
Bananen, richtig? Sieht nicht gut aus, oder? Wie Sie sehen, werfen diese Methodenaufrufe einen zu großen Einblick in das Geschäft anderer Objekte. Die wichtigste und offensichtlichste negative Konsequenz ist das Ändern einer Reihe dieser Methodenaufrufe überall, wenn sich die Struktur dieser Objekte ändern muss - was letztendlich auch der Fall sein wird, da die einzige Konstante in der Softwareentwicklung die Änderung ist. Außerdem sieht es wirklich unangenehm aus, für die Augen überhaupt nicht leicht. Wenn Sie nicht wissen, dass dies ein problematischer Ansatz ist, können Sie dies mit Rails ohnehin sehr weit gehen - ohne Sie anzuschreien. Viel Seil, denk dran?
Was können wir dagegen tun? Schließlich wollen wir diese Informationen irgendwie bekommen. Zum einen können wir unsere Objekte so zusammenstellen, dass sie unseren Bedürfnissen entsprechen, und wir können die Delegierung geschickt einsetzen, um unsere Modelle gleichzeitig schlank zu halten. Lassen Sie uns in etwas Code eintauchen, um Ihnen zu zeigen, was ich meine.
"Rubin
Klasse SpectreMember < ActiveRecord::Base has_many :operations has_many :spectre_agents
…
Ende
Klasse Operation < ActiveRecord::Base belongs_to :spectre_member
…
Ende
Klasse SpectreAgent < ActiveRecord::Base belongs_to :spectre_member
…
Ende
@ spectre_member.spectre_agents.all @ spectre_member.operations.last.print_assignment @ spectre_member.spectre_agents.find_by_id (1) .name
@ operation.spectre_member.name @ operation.spectre_member.number @ operation.spectre_member.spectre_agents.first.name
@ spectre_agent.spectre_member.number
"
"Rubin
Klasse SpectreMember < ActiveRecord::Base has_many :operations has_many :spectre_agents
…
def list_of_agents spectre_agents.all ende
def print_operation_details operation = Operation.last operation.print_operation_details end end
Klasse Operation < ActiveRecord::Base belongs_to :spectre_member
…
def spectre_member_name spectre_member.name end
def spectre_member_number spectre_member.number end
def print_operation_details schreibt: „Das Ziel dieser Operation ist # object. Das Ziel ist # Ziel “Ende Ende
Klasse SpectreAgent < ActiveRecord::Base belongs_to :spectre_member
…
def superior_in_charge setzt "Mein Chef ist die Nummer # spectre_member.number". end end
@ spectre_member.list_of_agents @ spectre_member.print_operation_details
@ operation.spectre_member_name @ operation.spectre_member_number
@ spectre_agent.superior_in_charge
"
Dies ist definitiv ein Schritt in die richtige Richtung. Wie Sie sehen, haben wir die Informationen, die wir erhalten wollten, in eine Reihe von Wrapper-Methoden gepackt. Anstatt viele Objekte direkt zu erreichen, abstrahierten wir diese Brücken und überließen es den jeweiligen Modellen, mit ihren Freunden über die Informationen zu sprechen, die wir benötigen.
Der Nachteil dieses Ansatzes ist, dass all diese zusätzlichen Wrapper-Methoden herumliegen. Manchmal ist es in Ordnung, aber wir möchten wirklich vermeiden, dass diese Methoden an mehreren Stellen aufbewahrt werden, wenn sich ein Objekt ändert.
Wenn möglich, ist der dedizierte Ort, an dem sie sich ändern können, auf ihrem Objekt - und nur auf ihrem Objekt. Die Verschmutzung von Objekten mit Methoden, die wenig mit dem eigenen Modell selbst zu tun haben, sollte ebenfalls beachtet werden, da dies immer eine potenzielle Gefahr für die Verwässerung einzelner Verantwortlichkeiten darstellt.
Wir können es besser machen. Wenn möglich, delegieren Sie Methodenaufrufe direkt an die zuständigen Objekte und versuchen Sie, Wrapper-Methoden so weit wie möglich zu reduzieren. Rails weiß, was wir brauchen und gibt uns das Handy delegieren
Klassenmethode, um den Freunden unseres Objekts mitzuteilen, welche Methoden wir benötigen.
Lassen Sie uns etwas aus dem vorherigen Codebeispiel betrachten und sehen, wo wir die Delegierung richtig einsetzen können.
"Rubin
Klasse Operation < ActiveRecord::Base belongs_to :spectre_member
Delegat: Name,: Nummer, bis:: spectre_member, Präfix: true
…
# spectre_member.name # end
# spectre_member.number # end
…
Ende
@ operation.spectre_member_name @ operation.spectre_member_number
Klasse SpectreAgent < ActiveRecord::Base belongs_to :spectre_member
Delegat: Nummer, an:: spectre_member, Präfix: true
…
def superior_in_charge setzt "Mein Chef ist die Nummer # spectre_member_number"
…
Ende
"
Wie Sie sehen, könnten wir die Methodendelegation etwas vereinfachen. Wir sind losgeworden Vorgang # spectre_member_name
und Vorgang # spectre_member_number
vollständig und SpectreAgent
muss nicht anrufen Nummer
auf spectre_member
nicht mehr-Nummer
wird direkt an die Klasse "origin" zurück delegiert SpectreMember
.
Falls dies zunächst etwas verwirrend ist, wie funktioniert das genau? Sie sagen dem Delegierten welche : Methodenname
es sollte delegieren zu:
welche :Klassenname
(mehrere Methodennamen sind auch in Ordnung). Das Präfix: wahr
Teil ist optional.
In unserem Fall hat er den Namen der schlangenumgebenden Klasse der empfangenden Klasse vor dem Methodennamen vorangestellt und uns den Aufruf ermöglicht operation.spectre_member_name
anstelle des möglicherweise mehrdeutigen operation.name
-wenn wir die Präfixoption nicht benutzt hätten. Das funktioniert wirklich gut mit gehört
und has_one
Vereinigungen.
Auf der hat viele
Auf der anderen Seite wird die Musik aufhören und Sie werden in Schwierigkeiten geraten. Diese Zuordnungen stellen Ihnen ein Sammlungs-Proxy zur Verfügung, das Ihnen NameErrors oder NoMethodErrors übergibt, wenn Sie Methoden an diese "Sammlungen" delegieren..
Um dieses Kapitel zum Thema AntiPatterns in Rails abzurunden, möchte ich etwas Zeit darauf verwenden, was bei SQL vermieden werden sollte. Active Record-Verknüpfungen bieten Optionen, die Ihnen das Leben erheblich erleichtern, wenn Sie wissen, was Sie meiden sollten. Finder-Methoden sind ein ganz eigenes Thema - und wir werden sie nicht ausführlich behandeln -, aber ich wollte ein paar allgemeine Techniken erwähnen, die Ihnen helfen, selbst wenn Sie sehr einfache schreiben.
Dinge, um die wir uns Sorgen machen sollten, sind das, was wir bisher gelernt haben. Wir möchten absichtsoffene, einfache und vernünftig benannte Methoden haben, um Dinge in unseren Modellen zu finden. Lassen Sie uns direkt in den Code eintauchen.
"Rubin
Klasse Operation < ActiveRecord::Base
has_many: Agenten
…
Ende
Klasse Agent < ActiveRecord::Base
gehört_zu: Betrieb
…
Ende
Klasse OperationsController < ApplicationController
def index @operation = Operation.find (params [: id]) @agents = Agent.where (operation_id: @ operation.id, licence_to_kill: true) Ende
"
Sieht harmlos aus, nein? Wir suchen nur nach einer Reihe von Agenten, die die Lizenz haben, unsere Ops-Seite zu töten. Denk nochmal. Warum sollte das OperationsController
in das Innere von Agent
? Ist dies wirklich das Beste, was wir tun können, um einen Finder zu kapseln? Agent
?
Wenn Sie denken, dass Sie eine Klassenmethode hinzufügen möchten Agent.find_licence_to_kill_agents
Was die Sucherlogik einschließt, machen Sie definitiv einen Schritt in die richtige Richtung - jedoch nicht annähernd genug.
"Rubin
Klasse Agent < ActiveRecord::Base
gehört_zu: Betrieb
def self.find_licence_to_kill_agents (operation) wo (operation_id: operation.id, licence_to_kill: true) ende ...
Ende
Klasse OperationsController < ApplicationController
def index @operation = Operation.find (params [: id]) @agents = Agent.find_licence_to_kill_agents (@operation) end end
"
Wir müssen ein bisschen engagierter sein. Zunächst nutzen wir die Assoziationen nicht zu unserem Vorteil, und die Kapselung ist auch suboptimal. Verbände wie hat viele
Kommen Sie mit dem Vorteil, den wir zu dem Proxy-Array hinzufügen können, das wir zurückgeben. Wir hätten das stattdessen tun können:
"Rubin
Klasse Operation < ActiveRecord::Base
has_many: Agenten
def find_licence_to_kill_agents self.agents.where (licence_to_kill: true) Ende…
Ende
Klasse OperationsController < ApplicationController
def index @operation = Operation.find (params [: id]) @agents = @ operation.find_licence_to_kill_agents end end
"
Das funktioniert sicher, ist aber auch nur ein kleiner Schritt in die richtige Richtung. Ja, der Controller ist ein bisschen besser und wir nutzen Modellassoziationen gut, aber Sie sollten immer noch misstrauisch sein Operation
befasst sich mit der Implementierung einer bestimmten Art von Agent
. Diese Verantwortung gehört wieder zum Agent
Modell selbst.
Benannte Bereiche sind damit sehr praktisch. Bereiche definieren verkettbare Methoden der sehr wichtigen Klasse für Ihre Modelle. Auf diese Weise können Sie nützliche Abfragen angeben, die Sie als zusätzliche Methodenaufrufe zusätzlich zu Ihren Modellzuordnungen verwenden können. Die folgenden zwei Ansätze für das Scoping Agent
sind gleichgültig.
"Rubin
Klasse Agent < ActiveRecord::Base belongs_to :operation
Gültigkeitsbereich: licenced_to_kill, -> where (licence_to_kill: true) end
Klasse Agent < ActiveRecord::Base belongs_to :operation
def self.licenced_to_kill wobei (licence_to_kill: true) ende endet
Klasse OperationsController < ApplicationController
def index @operation = Operation.find (params [: id]) @agents = @ operation.agents.licenced_to_kill end end
"
Das ist viel besser Falls die Syntax von Scopes neu für Sie ist, sind sie nur (durchtriebene) Lambdas - nicht unbedingt wichtig, um sie übrigens sofort zu untersuchen - und sie sind die richtige Art, Scopes seit Rails 4 zu nennen. Agent
Jetzt ist er für die Verwaltung seiner eigenen Suchparameter verantwortlich, und Verbände können nur das nachschlagen, was sie suchen müssen.
Mit diesem Ansatz können Sie Abfragen als einzelne SQL-Aufrufe durchführen. Ich persönlich benutze gerne Umfang
für seine explicitness. Scopes sind auch sehr praktisch, um in namentlich genannten Finder-Methoden zu verketten. Dadurch erhöhen sie die Möglichkeit, Code und DRY-Code wiederzuverwenden. Nehmen wir an, wir haben etwas mehr involviert:
"Rubin
Klasse Agent < ActiveRecord::Base belongs_to :operation
Geltungsbereich: licenced_to_kill, -> where (licence_to_kill: true) Gültigkeitsbereich: womanizer, -> where (womanizer: true) Gültigkeitsbereich: bond, -> where (Name: 'James Bond') Gültigkeitsbereich: gambler, - > wo (Spieler: wahr) Ende
"
Wir können jetzt alle diese Bereiche verwenden, um komplexere Abfragen benutzerdefiniert zu erstellen.
"Rubin
Klasse OperationsController < ApplicationController
def index @operation = Operation.find (params [: id]) @double_o_agents = @ operation.agents.licenced_to_kill end
def show @operation = Operation.find (params [: id]) @bond = @ operation.agents.womanizer.gambler.licenced_to_kill end
… Ende
"
Sicher, das funktioniert, aber ich möchte Ihnen vorschlagen, noch einen Schritt weiter zu gehen.
"Rubin
Klasse Agent < ActiveRecord::Base belongs_to :operation
Geltungsbereich: licenced_to_kill, -> where (licence_to_kill: true) Gültigkeitsbereich: womanizer, -> where (womanizer: true) Gültigkeitsbereich: bond, -> where (Name: 'James Bond') Gültigkeitsbereich: gambler, - > wo (Spieler: wahr)
def self.find_licenced_to_kill licenced_to_kill end
def self.find_licenced_to_kill_womanizer womanizer.licenced_to_kill end
def self.find_gambling_womanizer gambler.womanizer end
…
Ende
Klasse OperationsController < ApplicationController
def index @operation = Operation.find (params [: id]) @double_o_agents = @ operation.agents.find_licenced_to_kill end
def show @operation = Operation.find (Parameter [: id]) @bond = @ operation.agents.find_licenced_to_kill_womanizer #oder @bond = @ operation.agents.bond end
…
Ende
"
Wie Sie sehen, profitieren wir mit diesem Ansatz von den Vorteilen der korrekten Kapselung, Modellzuordnungen, Wiederverwendung von Code und der ausdrucksstarken Benennung von Methoden - und dies alles, während Sie einzelne SQL-Abfragen durchführen. Kein Spaghetti-Code mehr!
Wenn Sie Angst haben, gegen das Gesetz des Demeter-Dings zu verstoßen, werden Sie erfreut sein zu hören, dass wir keine Punkte hinzufügen, indem wir in das zugehörige Modell greifen, sondern nur an ihr eigenes Objekt ketten, und begehen keine Demeter-Verbrechen.
Aus der Sicht eines Anfängers denke ich, Sie haben viel über den besseren Umgang mit Rails-Modellen gelernt und erfahren, wie sie robuster modelliert werden können, ohne einen Ha