向困惑的初学者解释 Ruby 的单例类(特征类)
什么是特征级?
辛格尔顿模式
特征类
类 << 自身表示法
如果你使用类方法,实际上就是在使用特征类。
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
本文原文发布于我的博客
当我开始查找特征类究竟是什么时,我感到非常困惑。这种困惑并非源于特征类本身的复杂性,而是因为零星的解释不够清晰,而且没有文章能够全面阐述其概念。
所以我的目标是通过解释 Ruby 的 Eigenclass 究竟是什么以及为什么它被称为“单例类”来帮助大家理解。
要理解特征类,你只需要掌握类和实例的基本概念。温馨提示:
- 类是我们用来定义对象的工具。
- 类的实例是由我们的类创建的对象。
class MyClass
# Body of class
end
MyClass # => class
MyClass.new # => Instance of MyClass
什么是特征级?
这是我最难找到的信息:Eigenclass 被称为 Singleton 类,因为它是一个遵循 Singleton 模式的类。
辛格尔顿模式
单例模式是一种面向对象编程模式,它确保某个类只有一个实例。
Ruby 使用模块来实现单例模式:只需Include Singleton在类定义中写入即可。
这个模块实际上隐藏了该:new方法,MySingletonObject.new否则总是会报错。取而代之的是,它会提供一个实例方法,该方法始终返回类的同一个唯一实例。
require 'singleton'
class NotSingleton
# 'initialize' is called everytime an instance is created
def initialize
puts 'This will be printed many times'
end
end
class MySingleton
include Singleton
# 'initialize' is called everytime an instance is created
def initialize
puts 'This will be printed once'
end
end
NotSingleton.new # => 'This will be printed many times'
NotSingleton.new # => 'This will be printed many times'
NotSingleton.new # => 'This will be printed many times'
MySingleton.instance # => 'This will be printed once'
MySingleton.instance # Nothing is printed
MySingleton.instance # Nothing is printed
如果你想要限制一个类,使其永远只能创建自身的一个实例,那么这将非常有用。
特征类
当你创建一个类的实例时,Ruby 会创建一个隐藏类,它本质上是原类的副本,但只属于当前实例。这就是特征类(Eigenclass)。如果你修改了第一个实例的特征类,它不会影响其他实例。
# This class is NOT a singleton
class ExampleObject
def print_hello
puts 'Hello'
end
end
object1 = ExampleObject.new
object2 = ExampleObject.new
object1.print_hello # => 'Hello'
object2.print_hello # => 'Hello'
def object2.print_hello
puts 'Bonjour'
end
object1.print_hello # => 'Hello'
object2.print_hello # => 'Bonjour'
搞定了!对象 object2 并非定义在 ExampleObject 类的作用域内,而是由它携带的该类的副本定义。因此,通过在 object2 中重新定义一个方法,我们相当于“打开了特征类”,并仅针对该对象修改了属性。
由于特征类只能存在一个实例,因此有时被称为单例类,尽管 ExampleObject 本身并非单例类。只有它的实例的特征类才是单例,因为它们各自都是唯一的。
类 << 自身表示法
前面的例子可以改写成这样:
class ExampleObject
def print_hello
puts 'Hello'
end
end
object1 = ExampleObject.new
object2 = ExampleObject.new
class << object2
def print_hello
puts 'Aloha'
end
end
object1.print_hello # => 'Hello'
object2.print_hello # => 'Aloha'
如您所见,class << object2 语法用于访问 object2 的 Eigenclass。
现在要讲一个巧妙的技巧:你可能知道,在 Ruby 中,类也是对象。在 Ruby 中,一切皆对象!
任何类的类始终都是类。我喜欢 Ruby 的这一点:它看似毫无道理,但实际上却自有其道理。
你可以自己尝试一下:
# We can create ExampleObject like this :
class ExampleObject
end
# Or like this :
ExampleObject = Class.new
ExampleObject.new.class # => ExampleObject
ExampleObject.class # => Class
Class.class # => Class
ExampleObject.class.class.class.class # => Class
所以,我们刚才看到 ExampleObject 类也是一个对象,更准确地说,它是 Class 类的一个实例。这必然意味着它也有自己的特征类!
如果类“ExampleObject”的实例的特征类是类“ExampleObject”的某种副本,那么类“ExampleObject”的特征类必然是类“Class”的某种副本。
# Instance of class ExampleObject
ExampleObject.new # => Eigenclass : a copy of ExampleObject
# Instance of class Class
ExampleObject # => Eigenclass : a copy of Class
现在,假设我们要访问 ExampleObject 类的 Eigenclass。
我们可以这样做:
class ExampleObject
# Either inside the definition of the class
class << self
def class_print_hello
puts 'Hello'
end
end
end
ExampleObject.class_print_hello # => 'Hello'
# or outside
class << ExampleObject
def class_print_hello
puts 'Sayonara'
end
end
ExampleObject.class_print_hello # => 'Sayonara'
如您所见,我们并非在类的实例上调用该方法(ExampleObject.new),而是在类本身上调用该方法(ExampleObject)。但这没关系,因为 ExampleObject 本身就是 Class 的一个实例。
如果你使用类方法,实际上就是在使用特征类。
如果你使用 Rails,你的模型中通常会包含以下内容:
class MyModel
def self.print_hello
puts 'Hello'
end
end
MyModel.print_hello # => 'Hello'
如您所见,在类定义中定义 self 对象的方法与使用 class << self 打开 Eigenclass 完全相同。
`self.printHello` 也被称为类方法,以区别于实例方法。请参见以下内容:
class MyModel
# Class Method
def self.print_hello
puts 'Hello'
end
# Instance Method
def print_bonjour
puts 'Bonjour'
end
end
MyModel.print_hello # => 'Hello'
MyModel.new.print_hello # => ERROR
MyModel.print_bonjour # => ERROR
MyModel.new.print_bonjour # => 'Bonjour'
所以你可以看到,你类的 Class 方法实际上只是你类的 Eigenclass 的一个 Instance 方法。
不要犹豫,把最后那句话重复给公司的实习生们听:他们会认为你是 Ruby 天才!
文章来源:https://dev.to/samuelfaure/explaining-ruby-s-singleton-class-eigenclass-to-confused-beginners-cep