通过"Ruby编程语言"并尝试实现第7章"对象创建和初始化"部分末尾的Singleton示例
本章使用"点"类,该类逐渐扩展为包括类实例变量和类方法,以允许记录"点统计" - 创建的点数及其平均值。
能够使其正常工作:无需单例即可工作
当我重构为具有一个 PointStats
Singleton 类和一个Point
类时,我发现当类是第一个类时PointStats
我得到一个undefined method x
错误,或者当Point
类是第一个时出现uninitialized constant PointStats
错误。
在OSX上运行1.8,尽管也在另一台通过rvm运行2.0的机器上尝试过 - 感兴趣的行也有相同的结果。
我一定缺少一些关于如何在 Ruby 中让多个类协同工作的非常基本的东西。
我做错了什么?据我所知,我完全遵循了书中的例子,但每个类似乎都要求首先定义另一个类。
首先使用 PointStats 类重构代码,如文本所示:
#!/usr/bin/ruby -w
require 'singleton'
class PointStats
include Singleton
def initialize
@n, @totalX, @totalY = 0, 0.0, 0.0
end
def record(point)
@n += 1
@totalX += point.x
@totalY += point.y
end
def report
puts "#{@n} -- points"
puts "#{@totalX/@n} -- Average x"
puts "#{@totalY/@n} -- Average y"
end
end
class Point
def initialize(x,y)
@x,@y = x,y
PointStats.instance.record(self)
end
ORIGIN = Point.new(0,0)
UNIT_X = Point.new(1,0)
UNIT_Y = Point.new(0,1)
include Enumerable
include Comparable
attr_reader :x, :y
def to_s
"(#{@x},#{@y})"
end
def +(other)
Point.new(@x + other.x, @y + other.y)
rescue
raise TypeError,
"Point like argument expected"
end
def -@
Point.new(-@x, -@y)
end
def *(scalar)
Point.new(@x*scalar, @y*scalar)
end
def coerce(other)
[self, other]
end
def [](index)
case index
when 0, -2: @x
when 1, -1: @y
when :x, "x": @x
when :y, "y": @y
else nil
end
end
def each
yield @x
yield @y
end
def ==(o)
if 0.is_a? Point
@x==o.x && @y==o.y
else
false
end
end
def eql?(o)
if o.instance_of? Point
@x.eql?(o.x) && @y.eql?(o.y)
else
false
end
end
def hash
code = 17
code = 37*code + @x.hash
code = 37*code + @y.hash
code
end
def <=>(other)
return nil unless other.instance_of? Point
@x**2 + @y**2 <=> other.x**2 + other.y**2
end
def self.sum(*points)
x = y = 0
points.each { |p| x+=p.x; y+=p.y }
Point.new(x,y)
end
end
编辑:谢谢@cozyconemotel - 使用了两种公共方法,让new
以更预期的方式运行,并为其他用途添加unrecorded
:
class Point
attr_reader :x, :y
def initialize(x,y)
@x,@y = x,y
PointStats.instance.record(self)
end
def self.unrecorded(x,y)
instance = new(x,y)
end
ORIGIN = Point.unrecorded(0,0)
UNIT_X = Point.unrecorded(1,0)
UNIT_Y = Point.unrecorded(0,1)
# and the rest follows ...
正如@guitarman所指出的,因为调用PointStats.instance.record
需要Point#x and Point#y
,你必须在initialize
之前移动到attr_reader :x, :y
才能使其工作。
class Point
attr_reader :x, :y
def initialize(x,y)
@x,@y = x,y
PointStats.instance.record(self)
end
#(rest of the class def..)
end
不过,我在您的代码中发现了另一个问题。在Point
的定义中,您将创建 3 个实例:
ORIGIN = Point.new(0,0)
UNIT_X = Point.new(1,0)
UNIT_Y = Point.new(0,1)
问题是这些也会触发对PointStats.instance.record
的调用,因此会发生这种情况
> p1 = Point.new(10,10)
=> #<Point:0x007ff7935d6f78 @x=10, @y=10>
> p2 = Point.new(20,20)
=> #<Point:0x007ff7935f4b68 @x=20, @y=20>
> PointStats.instance.report
5 -- points
6.2 -- Average x
6.2 -- Average y
=> nil
您应该做的一件事是将new
设为私有并创建一个调用 record
的工厂方法。喜欢这个:
class Point
attr_reader :x, :y
private_class_method :new
def initialize(x,y)
@x,@y = x,y
end
def self.generate(x,y)
instance = new(x,y)
PointStats.instance.record(instance)
instance
end
ORIGIN = new(0,0)
UNIT_X = new(1,0)
UNIT_Y = new(0,1)
#(here goes the rest..)
end
如果您只想同时使用new
(创建新实例而不记录(和generate
(创建记录的新实例(,则不必将new
设为私有。
这是因为您将 Point 类的初始化方法中的新点传递到 PointStats 类的record(self)
方法,您可以在点通过 attr_reader :x, :y
定义 getter 方法之前访问point.x
。attr_reader :x, :y
移动到 class Point
内的第一行,因为您的脚本将从上到下解释。