C'est vraiment juste aussi Brian Campbell solution. Si vous aimez cela, veuillez upvote sa réponse, trop: il a fait tout le travail.
#!/usr/bin/env ruby
class Object; def eigenclass; class << self; self end end end
module LogFileReader
class LogFileReaderNotFoundError < NameError; end
class << self
def create type
(self[type] ||= const_get("#{type.to_s.capitalize}LogFileReader")).new
rescue NameError => e
raise LogFileReaderNotFoundError, "Bad log file type: #{type}" if e.class == NameError && e.message =~ /[^: ]LogFileReader/
raise
end
def []=(type, klass)
@readers ||= {type => klass}
def []=(type, klass)
@readers[type] = klass
end
klass
end
def [](type)
@readers ||= {}
def [](type)
@readers[type]
end
nil
end
def included klass
self[klass.name[/[[:upper:]][[:lower:]]*/].downcase.to_sym] = klass if klass.is_a? Class
end
end
end
def LogFileReader type
Ici, nous créons une méthode globale (plus comme une procédure, en fait) appelés LogFileReader
, qui est le même nom que notre module LogFileReader
. C'est légal en Ruby. L'ambiguïté est résolu comme ceci: le module sera toujours préférable, sauf quand c'est évidemment un appel de méthode, c'est à dire vous soit le mettre entre parenthèses, à la fin (Foo()
) ou de passer un argument (Foo :bar
).
C'est un truc qui est utilisé dans quelques endroits dans la stdlib, et aussi dans le Camping et d'autres cadres. Parce que des choses comme include
ou extend
ne sont pas réellement des mots-clés, mais les méthodes ordinaires qui prennent ordinaire paramètres, vous n'avez pas à passer un effectif Module
comme argument, vous pouvez également passer quelque chose qui évalue à un Module
. En fait, cela fonctionne même pour l'héritage, il est parfaitement légal d'écrire class Foo < some_method_that_returns_a_class(:some, :params)
.
Avec cette astuce, vous pouvez la faire ressembler à vous héritez d'une classe générique, même si Ruby n'ont pas de génériques. Il est utilisé par exemple dans la délégation de la bibliothèque, où vous faites quelque chose comme class MyFoo < SimpleDelegator(Foo)
, et ce qui se passe, c'est que l' SimpleDelegator
méthode crée dynamiquement et renvoie un anonyme sous-classe de la SimpleDelegator
classe, les délégués de tous les appels de méthode à une instance de l' Foo
classe.
Nous utilisons une astuce similaire ici: nous allons créer dynamiquement un Module
, qui, lorsqu'il est mélangé dans une classe, va automatiquement enregistrer la classe avec l' LogFileReader
de registre.
LogFileReader.const_set type.to_s.capitalize, Module.new {
Il y a beaucoup de choses dans juste cette ligne. Nous allons commencer à partir de la droite: Module.new
crée un nouveau anonyme module. Le bloc passé, elle devient le corps du module, c'est essentiellement le même que l'utilisation de l' module
mot-clé.
Maintenant, sur const_set
. C'est une méthode pour la définition d'une constante. Donc, c'est la même chose que de dire FOO = :bar
, à l'exception que l'on peut passer dans le nom de la constante en tant que paramètre, au lieu d'avoir à connaître à l'avance. Puisque nous sommes à l'appel de la méthode sur l' LogFileReader
module, la constante sera défini à l'intérieur de cet espace, OIE, il sera nommé LogFileReader::Something
.
Alors, quel est le nom de la constante? Eh bien, c'est l' type
argument passé à la méthode par capitalisation. Donc, lorsque je passe en :cvs
, la constante sera LogFileParser::Cvs
.
Et que faisons-nous définir la constante? À notre nouvellement créé anonyme module, qui n'est plus anonyme!
Tout cela est vraiment juste une longwinded façon de dire module LogFileReader::Cvs
, sauf que nous ne connaissions pas l' "Cvs" partie à l'avance, et ne pouvaient donc pas avoir écrit de cette façon.
eigenclass.send :define_method, :included do |klass|
C'est le corps de notre module. Ici, nous utilisons define_method
pour définir dynamiquement une méthode appelée included
. Et nous ne sommes pas réellement définir la méthode sur le module lui-même, mais sur le module de eigenclass (via une petite méthode d'assistance que nous avons défini ci-dessus), ce qui signifie que la méthode ne deviendra pas une méthode d'instance, mais plutôt "statique" de la méthode (en Java/.Termes NETS).
included
est en fait un crochet spécial de la méthode, qui est appelée par le Rubis de l'exécution, chaque fois qu'un module est inclus dans une classe, et la classe est transmis comme argument. Donc, notre module nouvellement créé a maintenant un crochet méthode qui vous permettra d'informer chaque fois qu'il est inclus quelque part.
LogFileReader[type] = klass
Et c'est ce que notre crochet méthode: il enregistre la classe qui est passée dans le crochet de la méthode dans l' LogFileReader
de registre. Et la clé qu'il l'enregistre sous, est l' type
argument de l' LogFileReader
méthode de la façon ci-dessus, qui, grâce à la magie des fermetures, il est en fait accessible à l'intérieur de l' included
méthode.
end
include LogFileReader
Et le dernier mais non le moindre, nous incluons l' LogFileReader
module dans l'anonymat module. [Note: j'ai oublié cette ligne dans l'exemple original.]
}
end
class GitLogFileReader
def display
puts "I'm a git log file reader!"
end
end
class BzrFrobnicator
include LogFileReader
def display
puts "A bzr log file reader..."
end
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class NameThatDoesntFitThePattern
include LogFileReader(:darcs)
def display
puts "Darcs reader, lazily evaluating your pure functions."
end
end
LogFileReader.create(:darcs).display
puts 'Here you can see, how the LogFileReader::Darcs module ended up in the inheritance chain:'
p LogFileReader.create(:darcs).class.ancestors
puts 'Here you can see, how all the lookups ended up getting cached in the registry:'
p LogFileReader.send :instance_variable_get, :@readers
puts 'And this is what happens, when you try instantiating a non-existent reader:'
LogFileReader.create(:gobbledigook)
Cette nouvelle version permet de trois différentes façons de définir LogFileReader
s:
- Toutes les classes dont le nom correspond au modèle
<Name>LogFileReader
sera automatiquement trouvé et enregistré en tant que LogFileReader
pour :name
(voir: GitLogFileReader
),
- Toutes les classes qui mélange dans l'
LogFileReader
module et dont le nom correspond au modèle <Name>Whatever
sera enregistrée pour l' :name
gestionnaire (voir: BzrFrobnicator
) et
- Toutes les classes qui mélange dans l'
LogFileReader(:name)
module, seront enregistrées pour l' :name
gestionnaire, quel que soit leur nom (voir: NameThatDoesntFitThePattern
).
Veuillez noter que c'est juste un très artificiel de démonstration. Il est, par exemple, certainement pas thread-safe. Il peut également présenter une fuite de mémoire. À utiliser avec prudence!