【问题标题】:Enabling changing value of an class instance attribute marked as 'attr_reader'启用标记为“attr_reader”的类实例属性的更改值
【发布时间】:2017-08-19 00:57:20
【问题描述】:

我有以下代码:

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 初始化类后限制xy 变量的值变化。但是,我希望在从类中初始化对象时允许这样做,如上面的代码所示。

但是当我调用obj = A.with_data 时出现以下错误:

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

这不应该在课堂上允许吗?我需要为此定义attr_writer 吗?这会危及封装。

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

版本: 红宝石 1.9.3

【问题讨论】:

  • 为什么不直接在initialize方法中设置实例变量呢?为什么要使用额外的 with_data 工厂方法?你试图达到什么在initialize 中无法做到的事情?如果initialize 是为变量赋值的唯一方法,那么它们以后就不能轻易更改。我错过了什么吗?
  • @spickermann 我在这里只放了最低要求的代码。我的实际课程中将有多个工厂方法。因此,工厂方法。
  • @Sid:你说你不想“危及封装”。但是您还想从A 操纵a 的内部状态。现在,aA 是两个完全不同的 对象,它们没有相同的aA 的一个实例,@ 987654337@ 是Class 的一个实例!所以,你说你想从A 设置a 的内部状态,这是一个完全不同的对象。但是,从 不同的 对象设置对象的内部状态几乎是违反封装的定义。你不能同时拥有它:要么你允许 a 的内部状态从一个…
  • …第三方对象(例如A),或者您不这样做。 (从技术上讲,您可以总是使用反射方法,例如sendinstance_variable_set 和朋友来违反封装。)

标签: ruby oop constructor encapsulation nomethoderror


【解决方案1】:

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

也就是说,您到底想达到什么目的?下面的类将初始化属性,通过阅读器公开它们,而不是让它们很容易从“外部”更改。这不正是你想要的吗?

class A
  attr_reader :x, :y

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

【讨论】:

  • 如果我的代码中的意图不清楚,我很抱歉。我不想公开构造函数并仅通过工厂方法启用对象创建,因为在我的实际代码中,它是一个具有多个工厂方法的相当复杂的对象。这是否有助于清除我的意图?
  • 我不确定您所说的“不想公开构造函数并仅通过工厂方法启用对象创建”是什么意思。 Ruby没有构造函数,all对象的创建总是只能通过工厂方法完成(truefalse除外、nilFloatIntegerRationalComplexSymbolStringArrayHash,当然还有 lambda 字面量)。
  • 构造函数是指initialize 方法。我不想公开它,以便通过它创建实例。我想要工厂方法来创建这些实例。
  • initialize 不是构造函数,它是初始化程序。 (嗯,实际上,它只是一个方法,就像其他所有方法一样。)“我不想公开它,以便通过它创建实例。” ——我不明白。首先,实例是用Class#allocate 创建的,而不是initialize;毕竟,initialize 是一个实例方法,IOW 它是在现有实例上调用的,所以它在逻辑上不可能创建实例。其次,你所说的“暴露”是什么意思? initialize 默认为 private,除非您明确指定为 publicprotected
【解决方案2】:

目的是限制 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

【讨论】:

  • 如果我的代码中的意图不清楚,我很抱歉。我不想公开构造函数并仅通过工厂方法启用对象创建,因为在我的实际代码中,它是一个具有多个工厂方法的相当复杂的对象。这是否有助于清除我的意图?
  • @Sid 我添加了另一个答案,但我仍然看不出它比这个答案更好。无论如何.. :)
  • 我知道,在上面发布的代码的上下文中,任何其他解决方案都可能看起来有点矫枉过正。但是想象一个类,它充当具有 30 多个实例变量的数据容器。我想针对不同的情况以不同的方式初始化这些实例变量,但不将该逻辑暴露给调用方法。因此不同的工厂方法。这将需要有一个私有构造函数并且只有读取器方法。但是,与 Java 不同的是,Ruby 甚至不能在类中设置值。因此,难题。如果有更好的方法来实现这一点,我很高兴听到
  • 我感觉您的实际难题可能是您试图在 Ruby 中复制类似 Java 的东西。如果您正在为实现您想要的语言而奋斗,那么可能是时候重新考虑您的方法是否符合习惯。但是由于您从未描述过实际问题,因此这里没有人可以帮助您。祝你好运 instance_varible_set 方法。
  • @Sid:“Ruby 甚至不能在类中设置值”——我不明白你的意思。我的意思是,如果 Ruby 不允许您设置实例变量,那么实例变量将毫无用处!
【解决方案3】:

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

【讨论】:

  • 谢谢,这有帮助。我希望动态方法的性能不差。
  • @Sid 没有问题。如果有可以解决的问题,请不要忘记选择并接受您的问题的答案。
  • 是的,如果有人回复,我会再给你一天的时间。然后接受答案。谢谢。
  • @Sid 一点也不着急。只是提醒一下其他问题;)
【解决方案4】:

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

【讨论】:

    猜你喜欢
    • 2023-01-08
    • 2018-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-20
    • 1970-01-01
    • 1970-01-01
    • 2021-09-05
    相关资源
    最近更新 更多