Reputation: 14346
The following class Point
contains a class instance variable @count
that counts the number of instances created. It seems to work fine:
class Point
@count = 0
class << self
attr_accessor :count
end
def initialize(position)
@position = position
self.class.count += 1
end
end
However, if I add a subclass ColoredPoint
and I want @count
to include the number of these created too:
class ColoredPoint < Point
def initialize(position, color)
super(position)
@color = color
end
end
This gives an error when calling ColoredPoint.new(1, 'red')
:
undefined method '+' for nil (NoMethodError)
self.class.count += 1
How can I make this work? Do I have to use a class variable (@@count
), even though I've read that "you should avoid them at all costs"?
Upvotes: 0
Views: 167
Reputation: 114237
You could approach it like this:
class Point
class << self
def count
@count ||= 0
end
def increment_count
superclass.increment_count if superclass <= Point
@count = count + 1
end
end
def initialize(position)
self.class.increment_count
@position = position
end
end
The line superclass.increment_count if superclass <= Point
is for subclasses of Point
. It will call increment_count
on the superclass if the superclass is Point
or one of its subclasses. This way, the count gets propagated up the inheritance chain:
Point.new(123)
Point.count #=> 1
ColoredPoint.count #=> 0
ColoredPoint.new(456, "red")
Point.count #=> 2
ColoredPoint.count #=> 1
If you want to handle things a little more fundamentally, you could override the .new
class method and do the counting there. This is a bit unusual but if you want to have the class keep track of its instances, it might be a good fit.
With this approach the explicit call inside initialize
isn't needed anymore. Likewise, it makes super
calls from initialize
in other subclasses optional. You can also declare the counting method protected
which limits the caller to subclasses of Point
(the classes, not their instances):
class Point
class << self
def count
@count ||= 0
end
def new(...)
increment_count
super
end
protected
def increment_count
superclass.increment_count if superclass <= Point
@count = count + 1
end
end
def initialize(position)
@position = position # <- no counting logic here
end
end
class ColoredPoint < Point
def initialize(position, color)
@position = position # <- no call to super needed (although recommended)
@color = color
end
end
Upvotes: 1
Reputation: 29588
Another option would be to use a separate class or module to track the count.
For instance something like this might work for you:
module ClassCounter
def self.extended(base)
base.prepend(InstanceMethods)
base.extend(ClassMethods)
_counter[base.counter_name] = 0
end
def self.counter
_counter.dup
end
module ClassMethods
def count
ClassCounter.counter[counter_name]
end
def counter_name
self.name
end
def counter_names
@counter_names ||= ancestors.map(&:name) & ClassCounter.counter.keys
end
def inherited(subclass)
ClassCounter._counter[subclass.counter_name] = 0
end
end
module InstanceMethods
def initialize(*)
self.class.counter_names.each do |name|
ClassCounter._counter[name] += 1
end
super(*)
end
end
def self._counter
@_counter ||= {}
end
end
This will count subclasses separately while tracking all of the subclass instances as instances of the parental class as well:
class Point
extend ClassCounter
end
class ColoredPoint < Point; end
class OpaquePoint < ColoredPoint; end
Result:
Point.new
Point.count
#=> 1
ColoredPoint.new
ColoredPoint.count
#=> 1
Point.count
#=> 2
OpaquePoint.new
OpaquePoint.count
#=> 1
ColoredPoint.count
#=> 2
Point.count
#=> 3
Upvotes: 1
Reputation: 6064
Use this way. Remember Point objects are counted separately from ColoredPoint objects.
class Point
@count = 0
class << self
attr_accessor :count
end
def initialize(position)
@position = position
self.class.count += 1
end
end
class ColoredPoint < Point
@count = 0
class << self
attr_accessor :count
end
def initialize(position, color)
super(position)
@color = color
end
end
Point.new([0, 0])
Point.new([0, 1])
ColoredPoint.new([1, 1], 'red')
ColoredPoint.new([2, 2], 'blue')
puts Point.count # Output: 2
puts ColoredPoint.count # Output: 2
Upvotes: 1
Reputation: 1461
Try to define class variable in the Point
:
@@count = 0
And increment it in the Point
constructor:
@@count += 1
Full example:
class Point
@@count = 0
def self.number
@@count
end
def initialize(position)
@position = position
@@count += 1
end
end
class ColoredPoint < Point
def initialize(position, color)
super(position)
@color = color
end
end
Point.new(1)
ColoredPoint.new(1, 'red')
ColoredPoint.new(1, 'blue')
# ptint 3
puts Point.number
Upvotes: 0