允许更改标记为 'attr_reader' 的 class 实例属性的值
Enabling changing value of an class instance attribute marked as 'attr_reader'
我有以下代码:
class A
attr_reader :x, :y
private_class_method :new
def self.with_data
a = new
a.x = 2
a.y = 'sid'
a
end
end
目的是在通过工厂方法 with_data
初始化 class 后限制 x
和 y
变量的值发生变化。但是,我希望在从 class 中初始化对象时允许这样做,如上面的代码所示。
但是当我调用 obj = A.with_data
时出现以下错误:
NoMethodError: undefined method `x='
class 内部不应该允许这样做吗?我需要为此定义 attr_writer
吗?那会危及封装。
此外,我不想为 class 中的每个属性定义一个私有 setter 方法,因为它可能 运行 到多达 30 个实例级变量。 ruby 是否提供任何功能来解决这个问题?
版本:
Ruby1.9.3
顾名思义,attr_reader
只会定义一个 getter,因此您也可以在 class 中使用访问器。
也就是说,您到底想达到什么目的?以下 class 将初始化属性,通过 reader 公开它们,并且不会使它们从 "outside" 轻易改变。这不正是你想要的吗?
class A
attr_reader :x, :y
def initialize
@x = 2
@y = 'sid'
end
end
The intent is to restrict changing values of x
and y
variables once
the class is initialized through the factory method with_data
class Foo
attr_reader :bar, :baz # <==== assures you only read, not write
def initialize
@bar = :bar
@baz = :baz
end
end
现在只能读取属性,不能写入属性:
foo = Foo.new
=> #<Foo:0x007ff6148f0a90 @bar=:bar, @baz=:baz>
foo.bar
#=> :bar
foo.bar = 2
#=> NoMethodError: undefined method `bar=' for #<Foo:0x007ff6148f0a90 @bar=:bar, @baz=:baz
所以你需要的是 Object#instance_variable_set
:
class A
attr_reader :x, :y
private_class_method :new
def self.with_data
a = new
a.instance_variable_set(:@x, 2)
a.instance_variable_set(:@y, 'sid')
a
end
end
用法:
a = A.with_data
#=> #<A:0x007ff37c979d30 @x=2, @y="sid">
a.x
#=> 2
a.x = 3
#=> NoMethodError: undefined method `x=' for #<A:0x007ff37c979d30 @x=2, @y="sid">
我必须承认,我不理解您使用 initialize
或 attr_writer
时遇到的困难。当你只有一个工厂方法时,我觉得最干净的解决方案是在 Ruby 中使用工厂方法的标准名称,即 new
:
class A
attr_reader :x, :y
def initialize(x, y) self.x, self.y = x, y end
def self.new
super(2, 'sid')
end
private
attr_writer :x, :y
end
如果您有多个工厂方法并且想绝对确保没有人意外调用 new
,这是一个很好的解决方案:
class A
attr_reader :x, :y
def initialize(x, y) self.x, self.y = x, y end
private_class_method :new
def self.with_data
new(2, 'sid')
end
private
attr_writer :x, :y
end
如果您真的、真的、真的必须这样做,您可以复制 new
在您的工厂方法中所做的事情。毕竟,new
的实现非常简单:
class Class
def new(*args, &block)
obj = allocate
obj.__send__(:initialize, *args, &block)
obj
end
end
我有以下代码:
class A
attr_reader :x, :y
private_class_method :new
def self.with_data
a = new
a.x = 2
a.y = 'sid'
a
end
end
目的是在通过工厂方法 with_data
初始化 class 后限制 x
和 y
变量的值发生变化。但是,我希望在从 class 中初始化对象时允许这样做,如上面的代码所示。
但是当我调用 obj = A.with_data
时出现以下错误:
NoMethodError: undefined method `x='
class 内部不应该允许这样做吗?我需要为此定义 attr_writer
吗?那会危及封装。
此外,我不想为 class 中的每个属性定义一个私有 setter 方法,因为它可能 运行 到多达 30 个实例级变量。 ruby 是否提供任何功能来解决这个问题?
版本: Ruby1.9.3
顾名思义,attr_reader
只会定义一个 getter,因此您也可以在 class 中使用访问器。
也就是说,您到底想达到什么目的?以下 class 将初始化属性,通过 reader 公开它们,并且不会使它们从 "outside" 轻易改变。这不正是你想要的吗?
class A
attr_reader :x, :y
def initialize
@x = 2
@y = 'sid'
end
end
The intent is to restrict changing values of
x
andy
variables once the class is initialized through the factory methodwith_data
class Foo
attr_reader :bar, :baz # <==== assures you only read, not write
def initialize
@bar = :bar
@baz = :baz
end
end
现在只能读取属性,不能写入属性:
foo = Foo.new
=> #<Foo:0x007ff6148f0a90 @bar=:bar, @baz=:baz>
foo.bar
#=> :bar
foo.bar = 2
#=> NoMethodError: undefined method `bar=' for #<Foo:0x007ff6148f0a90 @bar=:bar, @baz=:baz
所以你需要的是 Object#instance_variable_set
:
class A
attr_reader :x, :y
private_class_method :new
def self.with_data
a = new
a.instance_variable_set(:@x, 2)
a.instance_variable_set(:@y, 'sid')
a
end
end
用法:
a = A.with_data
#=> #<A:0x007ff37c979d30 @x=2, @y="sid">
a.x
#=> 2
a.x = 3
#=> NoMethodError: undefined method `x=' for #<A:0x007ff37c979d30 @x=2, @y="sid">
我必须承认,我不理解您使用 initialize
或 attr_writer
时遇到的困难。当你只有一个工厂方法时,我觉得最干净的解决方案是在 Ruby 中使用工厂方法的标准名称,即 new
:
class A
attr_reader :x, :y
def initialize(x, y) self.x, self.y = x, y end
def self.new
super(2, 'sid')
end
private
attr_writer :x, :y
end
如果您有多个工厂方法并且想绝对确保没有人意外调用 new
,这是一个很好的解决方案:
class A
attr_reader :x, :y
def initialize(x, y) self.x, self.y = x, y end
private_class_method :new
def self.with_data
new(2, 'sid')
end
private
attr_writer :x, :y
end
如果您真的、真的、真的必须这样做,您可以复制 new
在您的工厂方法中所做的事情。毕竟,new
的实现非常简单:
class Class
def new(*args, &block)
obj = allocate
obj.__send__(:initialize, *args, &block)
obj
end
end