Ruby 中 Singleton 类对象的秘密
嗨,我希望你准备好了,现在我们将尝试了解在使用 ruby 编码时所有地方的这个“隐藏”对象是什么。
如果您是一位好奇的程序员,您就会知道 Ruby 是一种面向对象的编程语言,除此之外,Ruby 中的一切都是对象,一切皆是对象。例如,当我们定义一个 `House` 类时,这个类也是一个对象,一个 `Class` 实例。我们可以在下面的代码片段中看到这一点,我们调用 `.class` 方法返回此对象的类名。
class House; end # House is an intance of `Class` House.class # => Class
这可能与您之前见过的不同,并且工作方式也与我的想法不同,所以我开始问这个语言特性对我作为一名程序员有什么影响?我没有一个好的答案,我搜索了更多关于这种行为的信息,直到我找到了 Singleton 类(我还找到了调用 Eigenclass、匿名类和特定于对象的类的参考资料),它负责 ruby 类方法,如下面的代码片段。
class House
def self.open
puts "...Opened"
end
end
House.open # => ...Opened我相信这段代码与我们每天编写的 ruby 代码没有什么不同。Singleton 类允许我们向预定义对象添加方法,这些方法只会影响定义此方法的对象,就像下面的代码片段一样。
class House
def lawn_situation
puts "...this is perfect"
end
end
my_home = House.new
neighbors_house = House.new
def my_home.lawn_situation
puts "...could be better"
end
neighbors_house.lawn_situation # => ...this is perfect
my_home.lawn_situation # => ...could be better这也解释了我们如何在前面的代码中创建`self.open`方法,因为`House`类是`Class`类的一个对象,所以我们也使用这个语法(`self.open`)为这个预定义的对象添加一个新方法。
现在,我明白语法“def self.open”和“def my_home.lawn_situation”负责访问这些对象的单例类以添加方法,而不是直接添加到它们的类中,从而允许这些新方法仅影响它们各自的对象。
我想现在我们可以理解这个称为 Singleton 类的对象是什么以及我们如何使用它,但我们还没有讨论它如何影响我们的现实生活,所以我们现在就讨论。
在与一些朋友的交谈中,提到了这个功能的一个用例,我们可以重写对象方法来帮助我们创建一些规范。下面,我们使用 Singleton 类来重写一个方法来帮助我们测试代码行为。
# Defining House class
class House
def self.close
self.close_back_door
self.close_windows
self.close_principal_door
nil
end
def self.close_back_door = puts "...Closed Back Door"
def self.close_windows = puts "...Closed Windows"
def self.close_principal_door = puts "...Closed Principal Door"
end
# Testing .close method
context "Error to close the house" do
def House.close_principal_door
raise EspecificException, "Error to close the principal door"
end
expect(House.close).to raise_error(EspecificException)
end特别是,了解这个 ruby 特性让我清楚地了解了我在开发新系统功能时创建的一个错误,我将尝试在更简单的环境中说明该问题。
class Book
def self.gift_a_friend(friend, book)
@friend = friend
@book = book
return send_now(@book) unless friend_has_this_book?
puts "#{@friend.name} already has the book '#{@book}'"
end
private
def self.friend_has_this_book?
puts "Verifying if #{@friend.name} already has the book '#{@book}'"
@friend_has_this_book ||= @friend.books.include?(@book)
end
def self.send_now(book)
puts "Sending the book '#{book}' to #{@friend.name}!"
@friend.add_book(book)
end
end
class Friend
attr_reader :name, :books
def initialize(name, books = [])
@name = name
@books = books
end
def add_book(book)
@books << book
end
end
joao = Friend.new('J. Moura')
guilherme = Friend.new('Guilherme')
Book.gift_a_friend(joao, 'Lord of the Rings')
# Verifying if J. Moura already has the book 'Lord of the Rings'
# Sending the book 'Lord of the Rings' to J. Moura!
Book.gift_a_friend(joao, 'Lord of the Rings')
# Verifying if J. Moura already has the book 'Lord of the Rings'
# J. Moura already has the book 'Lord of the Rings'
Book.gift_a_friend(guilherme, 'Lord of the Rings')
# Verifying if Guilherme already has the book 'Lord of the Rings'
# Guilherme already has the book 'Lord of the Rings'
guilherme
# => #
# WTF? Guilherme has not the book 'Lord of the Rings' 问题在于,在第二次调用 `.friend_has_this_book?` 方法时,它不会执行 `@friend.books.include?(@book)`,因为 `@friend_has_this_book` 实例变量值已经设置。Singleton 类仅创建一次(这解释了名称 Singleton),后续调用将仅访问保持其状态的现有 Singleton 类。
我希望之前就理解它,但对于想要深入了解 ruby 语言及其特性的人来说,这是一个很好的开始,这些知识将避免错误并帮助我们分析作为程序员将面临的问题。
希望你喜欢,再见。
我用来了解这个主题的一些来源: