3

在 PHP 类中,解析器处理__construct__destruct方法来实例化实例并在脚本退出或您使用 unset 时销毁它。当您扩展一个类时,您只需使用parent::__constructparent::__destruct运行可能需要在扩展的类上运行的任何清理代码。

现在在代表数据库数据并帮助您操作该数据的类的上下文中,我认为__destruct可以使用一种方法将当前值与从数据库中获取的原始值进行比较,并在必要时进行更新(在某些情况下总是只要主键值不能更改,请执行 UPDATE)。在 PHP 中实现这一点非常简单。

这种方法的主要优点是根据需要简单地快速操作类变量,然后让类在最后执行一次大更新。在运行几分钟的长脚本中,最好在 __construct 期间创建数据库实例、获取数据、关闭数据库连接,然后仅在几分钟的执行期间操作类变量。在 __destruct 上,打开一个新的数据库连接进行更新,然后关闭数据库连接并清理任何其他需要清理的东西。

我很好奇人们对这是否是一个好主意/坏做法的想法,但我的主要问题是这在 Ruby 中是否可行。

在 Ruby 中,当您实例化该类的实例时,您会运行 initialize 方法。Ruby 的等价物parent::__constructsuper在 Ruby 中。还有 Ruby 类的ObjectSpace.define_finalizeandfinalize方法。但是,据我了解, finalize 方法不应该能够引用调用它的实例。最重要的是,我找不到任何与parent::__destruct. 我怀疑那是因为没有等价物,因为似乎该finalize方法被明确设计为防止引用自身。

有没有人知道如何做到这一点?如果不是,那么转储 Ruby 类以取回资源并防止数据丢失的最佳实践是什么?是否每个人都在将类实例设置为 nil 之前调用了一个垃圾收集方法,还是有其他方法?

谢谢

4

2 回答 2

4

不,Ruby 没有 PHP 等价物__destruct,因为 PHP 会在其引用计数达到零时立即销毁对象,但 Ruby 销毁对象的速度很慢。Ruby 只是不时地标记和扫描对象。此外,Ruby 在扫描 C 代码的局部变量时是保守的。如果 C 本地可能指向某个对象,Ruby 拒绝销毁该对象。

Ruby 有ObjectSpace.define_finalizer,但很难用。一个对象可以有多个终结器。如果超类定义了终结器,您可以在同一对象上定义另一个终结器。问题是ObjectSpace.undefine_finalizer从对象中删除所有终结器。因此,如果超类删除了它的终结器,它也会删除你的终结器。

Ruby 仅在销毁对象后运行终结器。终结器不能引用对象。如果是这样,Ruby 永远不会破坏该对象,并且您会发生内存泄漏。终结器不得位于self或局部变量引用对象的范围内。

在下一个示例中,我将展示如何使用终结器来更新数据库。这是一个坏主意,因为 Ruby 运行终结器的速度很慢。数据库可能很长时间没有更新。我呼吁GC.start强制进行完整的垃圾收集。我计算机的 Ruby 拒绝销毁其中一个对象,因此还没有发生一次更新。

require 'forwardable'
require 'pstore'

# Create a database of people's ages.
People = PStore.new('people.pstore')
People.transaction do
  People['Alice'] = 20
  People['Bob'] = 30
  People['Carl'] = 40
  People['David'] = 50
  People['Eve'] = 60
end

# Shows people in database.  This can show old values if someone
# forgot to update the database!
def show_people(heading)
  People.transaction(true) do
    puts heading
    %w[Alice Bob Carl David Eve].each do |name|
      puts "  #{name} is #{People[name]} years old."
    end
  end
end

show_people("Before birthdays:")

# This is a person in the database.  You can change his or her age,
# but the database is only updated when you call #update or by this
# object's finalizer.
class Person
  # We need a PersonStruct for the finalizer, because Ruby destroys
  # the Person before calling the finalizer.
  PersonStruct = Struct.new(:name, :age, :oage)
  class PersonStruct
    def update(_)
      s = self
      if s.age != s.oage
        People.transaction { People[s.name] = s.oage = s.age }
      end
    end
  end
  private_constant :PersonStruct

  # Delegate name (r) and age (rw) to the PersonStruct.
  extend Forwardable
  def_delegators(:@struct, :name, :age, :age=)

  # Find a person in the database.
  def initialize(name)
    age = People.transaction(true) { People[name] }
    @struct = PersonStruct.new(name, age, age)
    ObjectSpace.define_finalizer(self, @struct.method(:update))
  end

  # Update this person in the database.
  def update
    @struct.update(nil)
  end
end

# Now give everyone some birthdays.
Person.new('Alice').age += 2
Person.new('Bob').age += 1
Person.new('Carl').age += 1
Person.new('David').age += 1
Person.new('Eve').age += 2

# I forgot to keep references to the Person objects and call
# Person#update.  Now I try to run the finalizers.
GC.start

# Did the database get updated?
show_people("After birthdays:")

我的电脑给出了这个输出:

Before birthdays:
  Alice is 20 years old.
  Bob is 30 years old.
  Carl is 40 years old.
  David is 50 years old.
  Eve is 60 years old.
After birthdays:
  Alice is 22 years old.
  Bob is 31 years old.
  Carl is 41 years old.
  David is 51 years old.
  Eve is 60 years old.

我在 Eve 的年龄上加了 2 岁,但在我检查数据库之前没有更新。解释 Ruby 的 C 代码可能Person.new('Eve')在某个局部变量中留下了对的引用,因此 Ruby 不会破坏该对象。如果您使用其他版本的 Ruby 或不同的 C 编译器,结果可能会发生变化。当程序退出时,Ruby 会运行任何剩余的终结器,所以更新确实发生了,但为时已晚。

于 2017-09-17T03:41:38.090 回答
3

as pst noted in his comment you don't need a destructor for ruby. Just set all referring variables to null ( ref = nil ) and the object will get deleted by garbage collection. You can not know exactly when its garbace collected (deleted). In addition you could ( not that I recommend ) write a proc that runs before actuall deletion of that object

ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc)
于 2012-08-21T17:41:03.843 回答