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​​ 语言及其特性的人来说,这是一个很好的开始,这些知识将避免错误并帮助我们分析作为程序员将面临的问题。

希望你喜欢,再见。

我用来了解这个主题的一些来源:

  • Ruby 单例类
  • 向困惑的初学者解释 Ruby 的 Singleton 类(Eigenclass)