Popularity Contest: Initializing Class-Level Instance Variab

Subclassing a class that uses class-level instance variables requires
that you initialize them for subclasses. Which of the following three
techniques do you prefer?

TECHNIQUE 1 - EXPLICIT INITIALIZATION

class Alpha
@foo = []
def self.m1; @foo << rand; end
def self.m2; @foo.map{ rand }; end
end

class Bravo < Alpha
@foo = []
end

TECHNIQUE 2 - ON-DEMAND TEST/INITIALIZATION

class Alpha
def self.m1; (@foo||=[]) << rand; end
def self.m2; (@foo||=[]).map{ rand }; end
end

class Bravo < Alpha

Nothing special here

end

TECHNIQUE 3 - PARENT CLASS HANDLES FOR SUB-CLASSES

class Alpha
@foo = []
def self.m1; @foo << rand; end
def self.m2; @foo.map{ rand }; end
def self.inherited(subklass)
subklass.instance_eval{ @foo=[] }
end
end

class Bravo < Alpha

Nothing special here

end

On Thu, 26 Oct 2006, Phrogz wrote:

Subclassing a class that uses class-level instance variables requires that
you initialize them for subclasses. Which of the following three techniques
do you prefer?

harp:~ > cat a.rb
require ‘rubygems’
require ‘attributes’

class A
class << self
attribute ‘a’ => 42
attribute(:b){ “#{ self.name }” } # deferred initialization!
end
end

class B < A
end

p A.a
p A.b
p B.a
p B.b

harp:~ > ruby a.rb
42
A
42
B

one can use traits too - but it’s pretty heavy if you don’t need all
it’s
features. btw, traits was writtern to solve exactly this common
problem.

regards.

-a

Joel VanderWerf wrote:

end

I prefer a variant of the above:

Oops, midway through that exercise I forgot we were talking about class
methods, not instance methods. The following runs correctly:

require ‘set’

class Alpha
def self.foo
@foo ||= []
end
def self.m1; foo << rand; end
def self.m2; foo.map{ rand }; end

note that the expression for contstructing foo is not

repeated twice

end

class Bravo < Alpha

Nothing special here

end

class Charlie < Alpha
def self.foo
@foo ||= Set.new # or some other collection class
end

This implies that all subclasses of Charlie will

(unless overridden) use a set for the #foo method

end

class Delta < Charlie

default to Charlie’s choice of Set as the foo collection

end

class Echo < Alpha
@foo = [1,2,3] # possible to specify in advance
end

[Alpha, Bravo, Charlie, Delta, Echo].each {|c| p c.m1}

[0.657784608794096]
[0.340975041442558]
#<Set: {0.191071815451883}>
#<Set: {0.971105015466828}>
[1, 2, 3, 0.58602201527139]

Phrogz wrote:

def self.m2; @foo.map{ rand }; end
end

class Bravo < Alpha
@foo = []
end

Or:

class Alpha
@foo = []
def self.m1; @foo << rand; end
def self.m2; @foo.map{ rand }; end
def self.inherited(base)
base.class_eval { @foo=[] }
end
end

class Bravo < Alpha
end

T.

Phrogz wrote:

TECHNIQUE 2 - ON-DEMAND TEST/INITIALIZATION

class Alpha
def self.m1; (@foo||=[]) << rand; end
def self.m2; (@foo||=[]).map{ rand }; end
end

class Bravo < Alpha

Nothing special here

end

I prefer a variant of the above:

class Alpha
def foo
@foo ||= []
end
def self.m1; foo << rand; end
def self.m2; foo.map{ rand }; end

note that the expression for contstructing foo is not

repeated twice

end

class Bravo < Alpha

Nothing special here

end

class Charlie < Alpha
def foo
@foo ||= Set.new # or some other collection class
end

This implies that all subclasses of Charlie will

(unless overridden) use a set for the #foo method

end

class Delta < Charlie

default to Charlie’s choice of Set as the foo collection

end

class Echo < Alpha
def initialize(*)
super
@foo = [1,2,3] # possible to specify in advance
end
end