为什么不能在Ruby中自行替换?

时间:2015-08-25 17:11:01

标签: ruby

编辑:对于那些批评我更换self的意图的人,你可以自由点击后退按钮,继续发展自己的意见,让我与我一起发展:)

我想知道是否有办法彻底删除self引用的对象并将其替换为新实例。

示例:

def refresh_from_server!
  self = newly_fetched_object_from_server
end

我不想return新对象。

似乎我必须构建自己的复制界面并调用self.copy_from(other_object),但也许某人有一个很酷的红宝石位分享效果更好!

- 编辑

由于有些人似乎不清楚这个问题,我希望instance.my_method!用该类的新实例完全替换instance

例如,假设我们有一个类

class Counter

  attr_accessor :count
  def initialize
    count = 0
  end

  def reset!
    # This is what I want to achieve.
    # Obviously in this case it would be trivial to write `self.count = 0`
    # Or return a new value
    # But I have a much more complex object in real life 
    # which copying would not be trivial
    # All I'm looking for is a bit of stylistic sugar to make my code look cooler
    # If it doesn't exist, I would love to know why
    self = Counter.new 
  end

  def up
    count += 1
  end

end

3 个答案:

答案 0 :(得分:1)

不,您无法替换self。您只能更改其部分/全部状态,但对象引用将保持不变。

无论如何,你为什么要这样做?如果你只是想捎带你的初始化逻辑(在我看来就是这种情况),一些重构将有所帮助:只需从两个地方调用共享方法。

class Counter

  attr_accessor :count

  def initialize
    init_state
  end

  def reset!
    init_state
  end

  def up
    self.count += 1
  end

  private

  def init_state
    self.count = 0
  end

end

答案 1 :(得分:0)

正如其他人已经指出的那样,self无法从封闭的实例中替换。如果需要用新实例替换实例,则需要从外部完成,例如在注册其类实例的类工厂中。

Bellow是使用委托人的最简单的例子,展示了我的意思。 SimpleDelegator代表Counter实例的简单包装:

  require 'delegate'

  class Counter
    attr_accessor :count

    def initialize
      @count = 0
    end
  end

  class CounterDecorator < SimpleDelegator
    def reset!
      __setobj__(__getobj__.class.new)
    end
  end

  c = CounterDecorator.new(Counter.new)
  p c.__getobj__.object_id
  c.count = 123
  p c.count
  c.reset!
  p c.__getobj__.object_id
  p c.count

  # produces following output
  20131160
  123
  20130900
  0

答案 2 :(得分:0)

尽管问题很旧,但仍然可以访问。我将尝试在“ 为什么不能用Ruby代替自己?”中的“ 为什么”中详细阐述。

self在这种情况下的使用

https://web.archive.org/web/20191217060940/https://www.honeybadger.io/blog/ruby-self-cheat-sheet/

在各种上下文中都可以使用self。您的问题是在实例方法的上下文中使用它,因此我将重点介绍它。

例如这种情况:

class SomeClass
  def some_method
    puts "#{self.class} - #{self.object_id} - #{self.inspect}"
  end
end
a = SomeClass.new
a.some_method
# prints : SomeClass - 47013616336320 - #<SomeClass:0x000055846bcd7b80>

请注意,self还有其他用法:例如它在类定义的范围内引用Class对象。例如

class SomeClass
  puts "#{self.class} - #{self.object_id} - #{self.inspect}"
end
# prints : Class - 47102719314940 - SomeClass

替换self

的预期效果

下面的代码演示了您的期望/期望(据我所知):

class Counter
  def some_method
    puts "#{self.class} - #{self.object_id} - #{self.inspect}"
  end
  def refresh!
    self = Counter.new # not possible
    # results in error : "Can't change the value of self"
  end
end
a = Counter.new
a.some_method
# prints : Counter - 47013616336320 - #<Counter:0x000055846bcd7b80>
a.refresh!
# now you wish a to point to a different object

但是其他参考呢?例如。假设您想要:

a = Counter.new
b = a
a.some_method
b.some_method
# both print same : Counter - 47013616336320 - #<Counter:0x000055846bcd7b80>
a.refresh!
# now you wish both a and b to point to the same (new) object

如果这样说,它会提示为什么不这样做。

为什么我们不能替换self

简短的回答是,它根本不是语言/解释器提供的东西。关于推理:@matthewd在this answer中以某种方式回答了问题:

  

所有ruby变量引用本质上都是指针(但不是   指针),用C语言来表达。

     

您可以对一个对象进行突变(假设它不是不变的),并且所有   引用它的变量将因此指向相同的位置(现在   变异)对象。但是改变变量的唯一方法是   指的是直接分配给该变量-每个变量   变量是一个单独的引用;您不能为单个参考添加别名   有两个名字。

简而言之:变量中可能有其他对该对象的引用,这些引用不在实例方法的范围内。这些不能被该实例方法操纵。

一种达到预期效果的方法

如果您想获得这种效果,而只想触摸Counter的代码,则可以将所有方法和状态移到内部类Counter::Inner上,并使Counter的行为像去耦引用一样。 Counter的唯一“状态”将是对Counter::Inner对象的引用,而Counter可以通过method_missing方法将接收到的所有调用委派给该引用。如果您使用的是refresh!,则可以像现在打算替换Counter一样替换self中的引用。现在,所有外部代码将间接使用新的Counter:Inner实例。

class Counter
  class Inner
    def some_method
      puts "#{self.class} - #{self.object_id} - #{self.inspect}"
    end
  end
  def initialize(*args)
    @reference = Inner.new(*args)
  end
  def method_missing(method_id, *args)
    @reference.send(method_id, *args)
  end
  def refresh!
    @reference = Inner.new
  end
end
a = Counter.new
b = a
a.some_method
b.some_method
# both print same : Counter::Inner - 46991238242100 - #<Counter::Inner:0x0000557a00203e68>
a.refresh!
a.some_method
b.some_method
# both print same : Counter::Inner - 46991238240000 - #<Counter::Inner:0x0000557a00202e00>

仅是档案馆的一个答案:-)我希望这能为将来的访问者提供有用的见识。