允许更改标记为' attr_reader'的类实例属性的值。

时间:2017-03-26 10:08:37

标签: ruby oop constructor encapsulation nomethoderror

我有以下代码:

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

一旦通过工厂方法x初始化类,目的是限制更改ywith_data变量的值。但是,我希望在从类中初始化对象时允许这样做,如上面的代码所示。

但是当我调用obj = A.with_data时,我收到以下错误:

  

NoMethodError:未定义的方法`x ='

这应该是否允许来自课堂内?我需要为此定义attr_writer吗?这会危及封装。

另外,我不想为类中的每个属性定义私有的setter方法,因为它可能遇到最多30个实例级变量。 ruby是否提供了解决此问题的任何功能?

版本: Ruby 1.9.3

4 个答案:

答案 0 :(得分:2)

所以你需要的是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">

答案 1 :(得分:1)

顾名思义,attr_reader只会定义一个getter,所以你也可以在类中使用访问器。

那就是说,你究竟想要实现什么目标?下面的类将初始化属性,通过阅读器公开它们,而不是让它们可以从&#34;外部&#34;中轻松更改。那不就是你想要的吗?

class A
  attr_reader :x, :y

  def initialize
    @x = 2
    @y = 'sid'
  end 
end

答案 2 :(得分:1)

  

目的是限制更改xy变量的值一次   该类通过工厂方法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

答案 3 :(得分:0)

我必须承认,我不理解您使用initializeattr_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