Métodos de módulo privado en Ruby
private-methods access-specifier (9)
Tengo una pregunta de dos partes
Mejores prácticas
- Tengo un algoritmo que realiza alguna operación en una estructura de datos usando la interfaz pública
- Actualmente es un módulo con numerosos métodos estáticos, todos privados, excepto por el método de interfaz pública.
- Hay una variable de instancia que debe compartirse entre todos los métodos.
Estas son las opciones que puedo ver, ¿cuál es la mejor ?:
- Módulo con métodos estáticos (''módulo'' en rubí)
- Clase con métodos estáticos
- Módulo Mixin para su inclusión en la estructura de datos
- Refactoriza la parte del algoritmo que modifica esa estructura de datos (muy pequeña) y crea una mezcla que llama a los métodos estáticos del módulo de algoritmo
Parte técnica
¿Hay alguna forma de hacer un método de Módulo privado ?
module Thing
def self.pub; puts "Public method"; end
private
def self.priv; puts "Private method"; end
end
El private
allí no parece tener ningún efecto , aún puedo llamar a Thing.priv
sin problema.
Una buena manera es como esto
module MyModule
class << self
def public_method
# you may call the private method here
tmp = private_method
:public
end
private def private_method
:private
end
end
end
# calling from outside the module
puts MyModule::public_method
Lamentablemente, private
solo se aplica a los métodos de instancia. La forma general de obtener métodos privados "estáticos" en una clase es hacer algo como:
class << self
private
def foo()
....
end
end
Es cierto que no he jugado con esto en módulos.
También hay Module.private_class_method
, que podría decirse que expresa más intención.
module Foo
def self.included(base)
base.instance_eval do
def method_name
# ...
end
private_class_method :method_name
end
end
end
Para el código en la pregunta:
module Thing
def self.pub; puts "Public method"; end
def self.priv; puts "Private method"; end
private_class_method :priv
end
Ruby 2.1 o más nuevo:
module Thing
def self.pub; puts "Public method"; end
private_class_method def self.priv; puts "Private method"; end
end
Creo que la mejor manera (y sobre todo la forma en que se escriben las bibliotecas existentes) es hacer una clase dentro del módulo que trata con toda la lógica, y el módulo simplemente proporciona un método conveniente, por ejemplo
module GTranslate
class Translator
def perform( text ); ''hola munda''; end
end
def self.translate( text )
t = Translator.new
t.perform( text )
end
end
module Writer
class << self
def output(s)
puts upcase(s)
end
private
def upcase(s)
s.upcase
end
end
end
Writer.output "Hello World"
# -> HELLO WORLD
Writer.upcase "Hello World"
# -> so.rb:16:in `<main>'': private method `upcase'' called for Writer:Module (NoMethodError)
El mejor patrón que he encontrado al hacer esto en Rails es renunciar a los módulos que quieren tener métodos privados y usar una clase Singleton en su lugar. No se siente bien, pero funciona y parece más limpio que otros ejemplos que he visto en esta pregunta.
Me encantaría escuchar otras opiniones sobre esto.
Ejemplo:
ErrorService.notify("Something bad happened")
class ErrorService
include Singleton
class << self
delegate :notify, to: :instance
end
def notify(message, severity: :error)
send_exception_notification(message)
log_message(message, severity)
end
private
def send_exception_notification(message)
# ...
end
def log_message(message, severity)
# ...
end
end
Hacer un módulo privado o clase
Las constantes nunca son privadas. Sin embargo, es posible crear un módulo o clase sin asignarlo a una constante.
Entonces, una alternativa a :private_class_method
es crear un módulo o clase privada y definir los métodos públicos en él.
module PublicModule
def self.do_stuff(input)
@private_implementation.do_stuff(input)
end
@private_implementation = Module.new do
def self.do_stuff(input)
input.upcase # or call other methods on module
end
end
end
Uso:
PublicModule.do_stuff("whatever") # => "WHATEVER"
Vea los documentos para Module.new y Class.new .
¿Qué ocurre con el almacenamiento de métodos como lambdas dentro de variables / constantes de clase?
module MyModule
@@my_secret_method = lambda {
# ...
}
# ...
end
Para prueba:
module A
@@C = lambda{ puts "C" }
def self.B ; puts "B"; @@C[] ; end
private # yeah, this has no sense, just for experiment
def self.D ; puts "D"; @@C[] ; end
end
for expr in %w{ A::B A.B A::C A.C A::D A.D }
eval expr rescue puts expr
end
Aquí vemos que C puede ser usado con éxito por B y D, pero no desde afuera.
Puede usar el método "incluido" para hacer cosas elegantes cuando se combina un módulo. Esto hace que piense lo que quiere:
module Foo
def self.included(base)
class << base
def public_method
puts "public method"
end
def call_private
private_method
end
private
def private_method
puts "private"
end
end
end
end
class Bar
include Foo
end
Bar.public_method
begin
Bar.private_method
rescue
puts "couldn''t call private method"
end
Bar.call_private