ruby - portable - opencobol para windows
Dependencias circulares en Ruby (3)
Si necesita acceder a una subclase de una superclase, existe una buena posibilidad de que su modelo se rompa (es decir, debería ser una clase).
Dicho esto, hay un par de soluciones obvias:
1) solo crea un archivo que requiera los archivos foo:
all_foos.rb:
require "foo.rb"
require "foo_sub.rb"
y elimine los requisitos de foo.rb y foo_sub.rb.
2) eliminar el requerimiento de foo.rb
3) eliminar el require de foo_sub.rb y poner el require en foo.rb después de la definición de la clase.
Ruby no es C ++, no se quejará de FooSub.SOME_CONSTANT hasta que llame a Foo # foo ();)
Digamos que tenemos dos clases, Foo y Foo Sub, cada una en un archivo diferente, foo.rb y foo_sub.rb respectivamente.
foo.rb:
require "foo_sub"
class Foo
def foo
FooSub.SOME_CONSTANT
end
end
foo_sub.rb:
require "foo"
class FooSub < Foo
SOME_CONSTANT = 1
end
Esto no va a funcionar debido a la dependencia circular; no podemos definir ninguna clase sin la otra. Hay varias soluciones que he visto. Dos de ellos quiero evitarlos: ponerlos en el mismo archivo y eliminar la dependencia circular. Entonces, la única otra solución que he encontrado es una declaración a futuro:
foo.rb:
class Foo
end
require "foo_sub"
class Foo
def foo
FooSub.SOME_CONSTANT
end
end
foo_sub.rb
require "foo"
class FooSub < Foo
SOME_CONSTANT = 1
end
Lamentablemente, no puedo hacer que funcione lo mismo si tengo tres archivos:
foo.rb:
class Foo
end
require "foo_sub_sub"
class Foo
def foo
FooSubSub.SOME_CONSTANT
end
end
foo_sub.rb:
require "foo"
class FooSub < Foo
end
foo_sub_sub.rb:
require "foo_sub"
class FooSubSub < FooSub
SOME_CONSTANT = 1
end
Si requiero foo_sub.rb, entonces FooSub es una constante no inicializada en foo_sub_sub.rb. ¿Alguna idea de cómo evitar esto sin ponerlos en el mismo archivo ni eliminar la dependencia circular?
Otra opción decente es usar la función de autocarga de Ruby.
Funciona así:
module MyModule
autoload :Class1, File.join(File.dirname(__FILE__), *%w[my_module class1.rb])
autoload :Class2, File.join(File.dirname(__FILE__), *%w[my_module class2.rb])
# Code for MyModule here
end
y se describe bien aquí:
http://talklikeaduck.denhaven2.com/2009/04/06/all-that-y-might-require
Sandi Metz explica una solución a este problema y cómo resolverlo muy bien en su libro Diseño Práctico Orientado a Objetos en Ruby (POODA).
Lo que ella sugiere (y me inclino a aceptar porque funcionó lo mejor para mí hasta ahora), es inyectar la subclase FooSub
en la clase magistral Foo
.
Esto se haría en foo.rb con:
1 class Foo
2 def initialize(foo_sub:)
3 end
4 end
para mantener el código limpio y mantenerlo fácilmente modificable, debe envolver el foo_sub
en un método contenedor para que su clase se vea así:
1 class Foo
2
3 attr_reader :foo_sub
4
5 def initialize(foo_sub:)
6 @foo_sub = foo_sub
7 end
8 end
(Aquí, attr_reader
está configurando un método llamado foo_sub
y luego lo que se pasa al valor del hash de inicialización es una instancia de foo_sub, por @foo_sub
tanto @foo_sub
(línea 6), se puede establecer en el valor del método foo_sub
).
Ahora puede tener su clase FooSub
sin requisitos, por lo que es independiente de todo:
1 class FooSub
2 SOME_CONSTANT = 1
3 end
y puede agregar un método a su clase Foo
que tenga acceso a #SOME_CONSTANT:
1 class Foo
2
3 attr_reader :foo_sub
4
5 def initialize(foo_sub:)
6 @foo_sub = foo_sub
7 end
8
9 def foo
10 foo_sub.SOME_CONSTANT
11 end
12 end
En realidad, con esto, está configurando un método que devuelve la instancia de foo_sub @foo_sub
(que se inyecta en la inicialización), con el método #SOME_CONSTANT agregado en él. Tu clase solo espera que todo lo que se inyecte en la inicialización responda a #SOME_CONSTANT. Para que funcione, tendría que inyectar su clase FooSub
al configurar Foo
en un REPL (por ejemplo, IRB o PRY):
PRY
[1]> require ''foo''
[2]> => true
[3]> require ''foo_sub''
[4]> => true
[5]> foo_sub = FooSub.new
[6]> => #<FooSub:0x007feb91157140>
[7]> foo = Foo.new(foo_sub: foo_sub)
[8]> => #<Foo:0x007feb91157735 @foo_sub=FooSub:0x007feb91157140>
[9]> foo.foo
[10]> => 1
si, sin embargo, le inyectaste algo más, terminarías con:
PRY
[1]> require ''foo''
[2]> => true
[3]> require ''foo_sub''
[4]> => true
[5]> foo_sub = FooSub.new
[6]> => #<FooSub:0x007feb91157140>
[7]> foo = Foo.new(foo_sub: ''something else as a string'')
[8]> => #<Foo:0x007feb91157735 @foo_sub=''something else as a string''>
[9]> foo.foo
[10]> => UNDEFINED CONSTANT #SOME_CONSTANT ERROR MESSAGE
No sé qué leería el mensaje de error real en la línea 10, pero piense en esas líneas. Este error podría ocurrir porque de hecho habrías intentado ejecutar el método #SOME_CONSTANT en la cadena ''algo más como una cadena'' o ''something else as a string''.SOME_CONSTANT
cadena''. ''something else as a string''.SOME_CONSTANT
que obviamente no funcionaría.