如何实现一个类的示例的get方法,并且仍然可以访问Ruby中的对象方法?

vlju58qv  于 2024-01-07  发布在  Ruby
关注(0)|答案(1)|浏览(136)

我需要将一个类的示例写入一个变量,并且能够访问它来获取它的值和访问它的方法。我能以某种方式实现这一点吗?
举例来说:

class A
  def initialize(value)
    @value = value
  end

  def preview
    puts "Class preview: #{@value}"
  end

  def something(param)
    puts "Something method: #{@value * param}"
  end
end

class B
  attr_reader :obj

  def set_object(obj)
    @obj = obj
  end
end

b = B.new
b.set_object(A.new(5))

b.obj # ==> 5
10 + b.obj # ==> 15

b.obj.preview # ==> "Class preview: 5"
b.obj.something(3) # ==> "Something method: 15"

字符串

xcitsw88

xcitsw881#

代码的基本问题是,您正在实现一个行为类似于数字的对象,但没有遵循Ruby的类似数字类型的协议,如Numeric类所述。
特别是,您缺少Numeric#coerce方法提供的arithmetic coercion protocol
在Ruby中,每当算术运算符不知道如何处理其操作数时,它将向操作数发送coerce消息,并告诉它使用它知道如何处理的一对操作数进行响应。
例如,当将+消息发送到10并将A的示例作为参数传递时,将调用方法Integer#+。因此,在这一行中:

10 + b.obj

字符串
在这里,您将消息+发送到Integer文本10(计算结果),并将表达式b.obj(即A的示例)的计算结果作为参数传递。
所以,我们在这里基本上是:

some_integer + some_a


当然,现在的问题是,Integer#+不知道如何将A的示例添加到自己。然而,每个算术操作都遵守 * 算术强制 * 协议,即Integer#+的实现看起来有点像这样:

class Integer
  def +(other)
    if other.is_a?(Integer)
      # I know what to do!
      # Do whatever internal magic computes the sum of two `Integer`s
    else
      coerced_self, coerced_other = other.coerce(self)
      coerced_self + coerced_other
    end
  end
end


你看到这把戏了吗?Integer#+不知道如何添加Integer s和A s,这并不奇怪,因为你今天才写了A,而Integer#+几乎是30年前写的。* 然而 *,由于Integer是一个标准的Ruby类,它假设A知道如何处理Integer s!
所以,Integer#+在这里所做的是,它调用Acoerce方法,并说“嘿,我不知道你是什么,但在这里我把自己作为参数传递,我希望你知道我是什么,所以请把我和你自己转换成知道如何把两者相加的东西”。
这意味着,我们需要为A实现一个coerce方法。coerce的协议如下:

  • coerce被发送到算术运算的 * 右操作数 *,带有一个参数,这是算术运算的 * 左 * 操作数。
  • coerce需要返回一对对象[coerced_left_operand, coerced_right_operand]
  • coerce需要确保这个过程最终终止,也就是说,两个强制返回值中的 * 至少有一个 * 应该 * 更接近于内建类型 *。

所以,让我们实现coerce

class A
  def coerce(other) = [other, @value]
end


现在,如果我们运行问题中的代码,我们得到:

#<A:0x0000000101021cd0 @value=5>
15
Class preview: 5
Something method: 15


因此,正如您所看到的,表达式10 + b.obj被正确地计算为15
代码的下一个问题是,你没有覆盖一些应该被对象覆盖的标准方法,我说的是像BasicObject#==Object#eql?Object#hashObject#to_s等方法。
特别是,为了显示一个对象的人类可读的调试表示而发送的消息是inspect。我们没有覆盖Object#inspect,所以我们得到了默认的实现,它包含关于类的信息,一个实现定义的标识符,以及一个示例变量及其值的列表。
我们需要重写inspect来更像这样工作:

class A
  def inspect = @value.inspect
end


现在,运行问题中的代码,产生所需的结果:

5
15
Class preview: 5
Something method: 15


在你的代码中还有一些其他的东西可以改进。

  • 由于A旨在与数字类似,因此它应该继承自Numeric
  • 由于A是类整数,它应该响应to_int
  • 任何响应to_int的东西,在逻辑上也应该响应to_i
  • 由于A是类似数字的,它应该实现算术运算。
  • 几乎总是,类应该重写to_s以提供更合理的字符串表示。
  • 几乎总是,类应该重写==以提供更合理的相等语义。
  • 几乎总是,类应该覆盖eql?hash,以提供更合理的集合成员资格和哈希语义。
  • 在Ruby中,setter(在Ruby中称为 attribute writers)不应该以set_为前缀,而应该命名为foo=
  • 不应该手动编写普通属性读取器和写入器,应该使用Module#attr_readerModule#attr_writerModule#attr_accessor来定义它们。
  • 对象在创建后应该是完全有效的,不需要调用任何setter或更改任何状态来使它们有效。

如果我们把这些放在一起,我们会得到这样的东西:

class A < Numeric
  include Comparable

  def initialize(value)
    super()
    @value = value
  end

  def to_int = @value
  alias to_i to_int

  def to_s = @value.to_s
  alias inspect to_s

  def +(other)
    case other
    when A
      self.class.new(@value + other.value)
    when Integer, Float, BigDecimal, Rational, Complex
      self.class.new(@value + other)
    else
      raise(TypeError, "Don't know how to add #{other.inspect} of class #{other.class}") unless other.respond_to?(:coerce)

      coerced_self, coerced_other = other.coerce(self)
      coerced_self + coerced_other
    end
  end

  def -(other)
    case other
    when A
      self.class.new(@value - other.value)
    when Integer, Float, BigDecimal, Rational, Complex
      self.class.new(@value - other)
    else
      raise(TypeError, "Don't know how to add #{other.inspect} of class #{other.class}") unless other.respond_to?(:coerce)

      coerced_self, coerced_other = other.coerce(self)
      coerced_self - coerced_other
    end
  end

  def *(other)
    case other
    when A
      self.class.new(@value * other.value)
    when Integer, Float, BigDecimal, Rational, Complex
      self.class.new(@value * other)
    else
      raise(TypeError, "Don't know how to add #{other.inspect} of class #{other.class}") unless other.respond_to?(:coerce)

      coerced_self, coerced_other = other.coerce(self)
      coerced_self * coerced_other
    end
  end

  def /(other)
    case other
    when A
      self.class.new(@value / other.value)
    when Integer, Float, BigDecimal, Rational, Complex
      self.class.new(@value / other)
    else
      raise(TypeError, "Don't know how to add #{other.inspect} of class #{other.class}") unless other.respond_to?(:coerce)

      coerced_self, coerced_other = other.coerce(self)
      coerced_self / coerced_other
    end
  end

  def coerce(other)
    case other
    when Integer, Float, BigDecimal, Rational, Complex
      [self.class.new(other), self]
    else
      [other, to_int]
    end
  end

  def <=>(other) = to_i <=> other.to_i

  def preview
    puts("Class preview: #{self}")
  end

  def something(param)
    puts("Something method: #{self * param}")
  end

  protected

  attr_reader(:value)
end

class B
  attr_reader :obj

  def initialize(obj)
    @obj = obj
  end
end

相关问题