2009-03-29 5 views
9

Ich bin immer noch neu bei Ruby und schreibe im Grunde nur mein erstes Mikro-Programm, nachdem ich Coopers Buch beendet habe. Ich wurde auf die Richtung hingewiesen, Affen zu vermeiden, aber das Problem ist, ich weiß nicht, was die Alternativen sind, um das gleiche Verhalten zu erreichen. Grundsätzlich möchte ich eine neue Methode hinzufügen, auf die jedes String-Objekt zugreifen kann. Der offensichtliche Affe-Patching Weg ist:Alternativen zu Affe-Patching-Kern-Klassen

class String 
    def do_magic 
    ...magic... 
    end 
end 

Ich erinnere mich, es gibt einen Weg mit String.send. Aber ich kann mich nicht erinnern, wie es gemacht wurde und wo ich es gelesen habe. Kann jemand auf Alternativen hinweisen, die mich diese Methode weiterhin für die String-Klasse und untergeordnete Objekte verfügbar machen lassen?

Antwort

15

Jede andere Möglichkeit, dies zu tun, wäre nur eine peinliche Syntax für Affen Patching. Es gibt Möglichkeiten, send und eval und alle möglichen Dinge, aber warum? Gehen Sie voran und machen Sie es auf die offensichtliche Art und Weise.

Sie möchten in großen Projekten oder wenn Sie Abhängigkeiten haben, auf Monkey Patching achten, da Sie Konflikte haben können, wenn mehrere Hände am gleichen Ort herumspielen. Dies bedeutet nicht, dass Sie nach einer alternativen Syntax suchen, die dasselbe bewirkt - das heißt, seien Sie vorsichtig, wenn Sie Änderungen vornehmen, die sich auf Code auswirken könnten, der nicht Ihr ist. Dies ist wahrscheinlich kein Problem in Ihrem speziellen Fall. Es ist nur etwas, das in größeren Projekten behandelt werden muss.

Eine Alternative in Ruby ist, dass Sie Methoden zu einem einzelnen Objekt hinzufügen können.

a = "Hello" 
b = "Goodbye" 
class <<a 
    def to_slang 
    "yo" 
    end 
end 
a.to_slang # => "yo" 
b.to_slang # NoMethodError: undefined method `to_slang' for "Goodbye":String 
+0

do_magic brauchen kann ich sehen, wie es wirklich hängt von der Situation und der Art des Projekts selbst ab. Und ich denke, das würde auch für alle do/do not-Paare gelten. In Anbetracht meines speziellen Falles, ja, macht es Sinn, dass es nicht schaden sollte. Immerhin, ich füge eine neue Methode hinzu. Danke für die Klarstellung! – dmondark

+0

Monkey Patching ist bestenfalls ein rutschiger Abhang. Es mag für kleine Teams in Closed-Source-Projekten elegant erscheinen, aber Sie wissen nie, wann Ihr Quellcode als Schmuckstück verpackt oder anderweitig von jemandem kopiert wurde, und wenn der String etwas anders aussehen soll, dann überschreibt eine Affe-Patch-Methode die andere, und es wird keine Warnung ausgegeben. Es ist ein Rezept für schwer zu findende Bugs. Dies ist besonders problematisch, wenn Sie Methoden von Basisklassen überschreiben, die in Ruby enthalten sind. – emery

2

Die object-Klasse definiert send und alle Objekte erben diese. Sie "senden" ein Objekt die send Methode. Die Parameter der Methode send sind der Name der Methode-Sie-wollen-zu-Aufruf als Symbol, gefolgt von allen Argumenten und einem optionalen Block. Sie können auch __send__ verwenden.

>> "heya".send :reverse 
=> "ayeh" 

>> space = %w(moon star sun galaxy) 
>> space.send(:collect) { |el| el.send :upcase! } 
=> ["MOON", "STAR", "SUN", "GALAXY"] 

bearbeiten ..

Sie wahrscheinlich die define_method Methode verwenden möchten:

String.class_eval { 
    define_method :hello do |name| 
    self.gsub(/(\w+)/,'hello') + " #{name}" 
    end 
} 

puts "Once upon a time".hello("Dylan") 
# >> hello hello hello hello Dylan 

Das Instanzmethoden ergänzt. So fügen Sie Klassenmethoden hinzu:

eigenclass = class << String; self; end 
eigenclass.class_eval { 
    define_method :hello do 
    "hello" 
    end 
} 

puts String.hello 
# >> hello 

Sie können jedoch keine Methoden definieren, die einen Block erwarten.

Es könnte eine gute Sache sein, von this chapter from Why's Poignant Guide zu lesen, Sie können zu "Dwemthy's Array" übergehen, um zu den Metaprogrammierungen zu gelangen.

+0

Ich habe gerade versucht, vorübergehend den Sprung in die Meta-Programmierung zu vermeiden. Aber ich denke, es ist einfach unvermeidlich, wenn ich an Hallo-Welt-Zeug vorbeikommen will. Meine Idee über die Verwendung eines Evals ist grundsätzlich Laufzeit-Monkeypatching. Aber ich sehe jetzt, wie alles andere genau das ist. – dmondark

6

Wenn Sie eine neue Methode hinzufügen möchten, die für jedes String-Objekt zugänglich ist, dann tun Sie es so, wie Sie es haben.

Eine gute Übung besteht darin, Ihre Erweiterungen zu Core-Objekten in einer separaten Datei (wie string_ex.rb) oder einem Unterverzeichnis (wie extensions oder core_ext) zu setzen. Auf diese Weise ist es offensichtlich, was erweitert wurde, und es ist leicht für jemanden zu sehen, wie sie erweitert oder verändert wurden.

Wo Affe-Patching schlecht gehen kann, wenn Sie ein vorhandenes Verhalten eines Core-Objekts ändern, das anderen Code verursacht, der die ursprüngliche Funktionalität erwartet, sich falsch zu verhalten.

1

Danke Jungs.

Alle vorgeschlagenen Implementierungsarbeiten. Noch wichtiger ist, dass ich gelernt habe, den Fall in der Hand abzuwägen und zu entscheiden, ob das erneute Öffnen von Kern- (oder Bibliotheks-) Klassen eine gute Idee ist oder nicht.

FWIW, ein Freund wies auf die send Implementierung, die ich suchte. Aber jetzt, wo ich es sehe, ist es noch näher an monkeypatching als alle anderen Implementierungen :)

module M 
    def do_magic 
    .... 
    end 
end 
String.send(:include, M) 
0

Als Alternative zu Funktionen Klassen oder Objekten angebracht wird, können Sie immer die funktionale Weg gehen:

class StringMagic 
    def self.do(string) 
    ... 
    end 
end 

StringMagic.do("I'm a String.") # => "I'm a MAGIC String!" 
0

Der "Affen-Patch", den Sie beschreiben, könnte in der Tat ein Problem sein, wenn jemand anderes Ihren Code benötigen würde (zum Beispiel als Edelstein). Wer möchte sagen, dass sie auch keine String-Methode hinzufügen wollen, die do_magic heißt? Eine Methode überschreibt die andere und dies kann schwierig zu debuggen sein. Wenn es Ihr Code eine Chance Open-Source sein wird, dann ist es am besten Ihre eigene Klasse zu erstellen:

class MyString < String 
    def initialize(str) 
    @str = str 
    end 
    def do_magic 
    ...magic done on @str 
    @str 
    end 
end 

Nun, wenn Sie Sie können

magic_str = MyString.new(str).do_magic