extend self in ruby
Folgender Code wurde mit ruby 1.9.3 getestet.
Klasse ist sowohl für Daten als auch für Verhalten gedacht
Lassen Sie uns diesen Ruby-Code betrachten.
1class Util2 def self.double(i)3 i*24 end5end67Util.double(4) #=> 8
Hier haben wir eine Util
Klasse. Aber beachten Sie, dass alle Methoden dieser Klasse Klassenmethoden sind. Diese Klasse hat keine Instanzvariablen. Normalerweise wird eine Klasse verwendet, um sowohl Daten als auch Verhalten zu transportieren, aber in diesem Fall hat die Util-Klasse nur Verhalten und keine Daten.
Ähnliche Hilfsprogramme in Ruby
Um nun eine Perspektive auf diese Diskussion zu bekommen, schauen wir uns einige Ruby-Methoden an, die ähnliche Dinge tun. Hier sind ein paar.
1require 'base64'2Base64.encode64('hello world') #=>"aGVsbG8gd29ybGQ=\n "34require 'benchmark'5Benchmark.measure { 10*2000 }67require 'fileutils'8FileUtils.chmod 0644, 'test.rb'910Math.sqrt(4) #=> 2
In allen obigen Fällen wird die Klassenmethode aufgerufen, ohne vorher eine Instanz zu erzeugen. Das ist also ähnlich wie bei Util.double
.
Aber schauen wir mal, welche Klasse all diese Objekte haben.
1Base64.class #=> Module2Benchmark.class #=> Module3FileUtils.class #=> Module4Math.class #=> Module
Das sind also keine Klassen sondern Module. Das wirft die Frage auf, warum die schlauen Jungs von ruby-core sie als Module implementiert haben, anstatt eine Klasse zu erstellen, wie ich es für Util getan habe.
Der Grund ist, dass Class zu schwer ist, um nur Methoden wie double
zu erstellen. Wie wir bereits besprochen haben, soll eine Klasse sowohl Daten als auch Verhalten haben. Wenn das einzige, was dich interessiert, das Verhalten ist, dann schlägt Ruby vor, es als Modul zu implementieren.
selbst erweitern ist die Antwort
Bevor ich mit der Diskussion von extend self
fortfahre, ist hier, wie meine Util
Klasse nach dem Wechsel von Class
zu Module
aussehen wird.
1module Util2 extend self34 def double(i)5 i * 26 end7end89puts Util.double(4) #=> 8
So wie funktioniert extend self
Zunächst wollen wir sehen, was extend macht.
1module M2 def double(i)3 i * 24 end5end67class Rechner8 extend M9end10puts Rechner.double(4)
Im obigen Fall erweitert Calculator
das Modul M
und somit sind alle Instanzmethoden des Moduls M
direkt für Calculator
verfügbar.
In diesem Fall ist Calculator
eine Klasse, die das Modul M
erweitert. Allerdings muss Calculator
keine Klasse sein, um ein Modul zu erweitern.
Nun versuchen wir eine Variante, bei der Calculator
ein Modul ist.
1modul M2 def double(i)3 i * 24 end5end67module Calculator8 extend M9end10puts Calculator.double(4) #=> 8
Hier ist Calculator ein Modul, das ein anderes Modul erweitert.
Nun, da wir verstehen, dass ein Modul ein anderes Modul erweitern kann, schauen wir uns den obigen Code an und fragen uns, warum das Modul M
überhaupt benötigt wird. Warum können wir die Methode double
nicht direkt in das Modul Calculator verschieben. Versuchen wir das mal.
1module Calculator2 extend Calculator34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Ich bin das Modul M
losgeworden und habe die Methode double
ins Modul Calculator
verschoben. Da das Modul M
weg ist, habe ich von extend M
auf extend Calculator
gewechselt.
Eine letzte Korrektur.
Innerhalb des Moduls Calculator was ist self
. self
ist das Modul Calculator
selbst. Es ist also nicht nötig, Calculator
zweimal zu wiederholen. Hier ist die endgültige Version
1module Calculator2 extend self34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8
Eine Klasse in ein Modul umwandeln
Jedes Mal, wenn ich auf Code wie extend self
stoße, hält mein Gehirn für einen Moment inne. Dann würde ich danach googeln. Werde darüber lesen. Drei Monate später wiederhole ich den ganzen Prozess.
Am besten lernt man es, wenn man es anwendet. Also habe ich angefangen, nach einem Fall zu suchen, den ich verwenden kann extend self
. Es ist keine gute Praxis, nach Code zu jagen, um eine Idee anzuwenden, die man im Kopf hat, aber hier wollte ich lernen.
Hier ist ein Schnappschuss von Methoden der Klasse Util
, die ich in einem Projekt verwendet habe.
1class Util2 def self.config2hash(file); end3 def self.in_cents(amount); end4 def self.localhost2public_url(url, protocol); end5end
Nachdem ich extend self
Code verwendet habe, wurde
1module Util2 extend self34 def config2hash(file); end5 def in_cents(amount); end6 def localhost2public_url(url, protocol); end7end
Viel besser. Es macht die Absicht klar und ich glaube, es ist im Einklang mit der Art und Weise, wie Ruby erwarten würde, dass wir es verwenden.
Eine weitere Verwendung im Einklang mit der Art und Weise, wie Rails extend self verwendet
Hier baue ich eine E-Commerce-Anwendung und jede neue Bestellung muss eine neue Bestellnummer von einer Drittanbieter-Verkaufsanwendung erhalten. Der Code könnte wie folgt aussehen. Ich habe die Implementierung der Methoden weggelassen, weil sie für diese Diskussion nicht relevant sind.
1class Order2 def amount; end3 def buyer; end4 def shipped_at; end5 def number6 @number || self.class.next_order_number7 end89 def self.next_order_number; 'A100'; end10end1112puts Order.new.number #=> A100
Hier könnte die Methode next_order_number
einen komplizierten Aufruf an ein anderes Verkaufssystem machen. Idealerweise sollte die Klasse Order
die Methode next_order_number
nicht exponieren. Wir können diese Methode also zu private
machen, aber das löst nicht das eigentliche Problem. Das Problem ist, dass das Modell Order
nicht wissen sollte, wie die neue Auftragsnummer generiert wird. Nun, wir können die Methode next_order_number
in eine andere Util
Klasse verschieben, aber das würde zu viel Abstand schaffen.
Hier ist eine Lösung mit extend self
.
1module Checkout2 extend self34 def next_order_number; 'A100'; end56 class Order7 def amount; end8 def buyer; end9 def shipped_at; end10 def number11 @number || Checkout.next_order_number12 end13 end14end1516puts Checkout::Order.new.number #=> A100
Viel besser. Die Klasse Order stellt die Methode next_order_number
nicht zur Verfügung und diese Methode befindet sich direkt in der gleichen Datei. Es ist nicht nötig, die Klasse Util
zu öffnen.