My goal is to initialize an instance variable without making use of the initialize method. I have this code:
class Animal
attr_reader :age
def initialize(age)
@age = age
end
end
class Sheep < Animal
attr_accessor :likes
def initialize(age)
super
@likes = []
end
end
sheep = Sheep.new(5)
sheep.likes << "grass"
The initialize
method in this subclass calls super
. This doesn't scale very nicely: If I change the signature of the superclass, I have to adjust it in all subclasses as well.
It would be nicer if I can initialize an instance variable like @likes = []
outside of the initialize
method within the class scope of Sheep
, like many other OO-languages can. However, that would make my variable an instance variable of the class object.
Here's a way I discovered that doesn't override the constructor:
class Sheep < Animal
attr_accessor :likes
def likes
@likes || @likes = []
end
end
That's much more elegant because readjusting signatures is not necessary, but it's still not perfect: Wouldn't Ruby check for non-nil
-ness of likes
when I access that instance variable? Is there a way to do this without sacrificing runtime or code elegance?
One thing you can do is call a method from the initialize
of Animal
, providing a hook for subclasses to add custom functionality:
class Animal
attr_reader :age
def initialize(age)
@age = age
setup_defaults
end
private
def setup_defaults
# NOOP by default
end
end
class Sheep < Animal
attr_accessor :likes
private
def setup_defaults
@likes = []
end
end
A second way, that you mention in your post, you can do this is use a custom def likes
instead of the attr_reader
/attr_accessor
:
def likes
@likes ||= [] # shorter way of doing what you have
end
As a third option, if you don't mind using initialize
(your primary concern seems to be possibly changing the superclass' signature), since you don't care about any the parameters to initializeSheep
is you can overwrite the initialize
like:
class Sheep < Animal
attr_accessor :likes
def initialize(*)
super
@likes = []
end
end
this is the same as doing something like def initialize(*args)
except you don't name the variable, and works since super
passes in the original arguments by default. Now, if you go back and change animal to have, say, a name
argument to its initialize
:
class Animal
attr_reader :age, :name
def initialize(name, age)
@name = name
@age = age
end
end
Sheep
still works without any changes.