How to properly destroy a class

2019-06-28 03:23发布

问题:

In Ruby, I have a DAO class, which is extended by a class that makes managing the connections easier, which is extended by a class that represents and manipulates data in a DB, which is further extended by another class. To use an animal metaphor it would look like this:

class Animal
 ...
end

class Mammal < Animal
 ...
end

class Feline < Mammal
 ...
end

class Cat < Feline
 ...
end

class Lion < Cat
 ...
end

...

In PHP, there is __destruct method that runs when you destroy/delete a class. And should that class extend another class, you simply add parent::__destruct() to the class's __destruct method like this:

public function __destruct() {
  // Clean up code for this class here
  ...

  // Execute clean up code for Parent class
  parent::__destruct();
}

I could have a similar method for all the classes except Animal. Since it doesn't extend anything, the parent::__destruct(); line is no longer valid.

However, as I understand it, Ruby doesn't have a method like this for its objects. A finalizer can be set, but I decided to just put in a cleanup method I can call whenever I want to destroy/delete a class. That would take care of anything that needed doing prior to my setting the class to nil.

This raises a new problem though. If the method is always named cleanup and I call lion_instance.cleanup, I assume it calls the Lion#cleanup. How then to get it to call the cleanup in class Cat and then Feline and on down the chain?

Or is this a wrong approach and you have a better idea?

回答1:

The Ruby idiom for this is to yield to a block which does work, and when the block returns, do cleanup. Ruby's built-in "File.open" does this:

File.open("/tmp/foo") do |file|
  file.puts "foo"
end

When the block ends, the file is closed for you, without you having to do anything. This is an excellent idiom. Here's how you might implement something like that:

class Foo

  def self.open(*args)
     foo = new(*args)
     yield foo
     foo.close
  end

  def initialize
    # do setup here
  end

  def close
    # do teardown here
  end

end

And to use it:

Foo.open do |foo|
  # use foo
end

Foo#close will be caused automatically after the end


This will work with subclassing as well. That's because class methods are inherited just as are instance methods. Here's the superclass:

class Superclass

  def self.open(*args)
    o = new(*args)
    yield o
    o.close
  end

  def initialize
    # common setup behavior
  end

  def close
    # common cleanup behavior
  end

end

and two derived classes:

class Foo < Superclass

  def initialize
    super
    # do subclass specific setup here
  end

  def close
    super
    # do subclass specific teardown here
  end

end

class Bar < Superclass

  def initialize
    super
    # do subclass specific setup here
  end

  def close
    super
    # do subclass specific teardown here
  end

end

to use:

Foo.open do |foo|
  # use foo
end

Bar.open do |bar|
  # use bar
end

If you really need to make sure that cleanup happens no matter what, then use an ensure clause in the class method:

  def self.open(*args)
     foo = new(*args)
     begin
       yield foo
     ensure
       foo.close
     end
  end

This way, cleanup happens even if there is an exception in the block.



回答2:

You can use ObjectSpace.define_finalizer

Something like:

class Animal
  def initialize
    ObjectSpace.define_finalizer(self, proc { # your code })
   end
end


回答3:

Well since no one answered your question about the method moving its way up the inheritance chain...

class Cat
  def rawr
    puts "rawr"
  end
end

class Kitty < Cat
  def rawr
    puts "meow"
    super
  end
end

Cat.new.rawr
"Rawr"

Kitty.new.rawr
"rawr"
"meow"

Within a method, you can access the superclass's method of the same name by calling super.