phrogz
October 26, 2006, 6:51am
1
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
phrogz
October 26, 2006, 7:35am
2
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
phrogz
October 26, 2006, 7:49am
3
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
October 26, 2006, 2:11pm
4
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
October 26, 2006, 7:39am
5
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