我正在尝试理解迈克尔哈特尔精彩的Rails教程中的一些代码。
在第8章中,我们向User模型添加了一些类方法:
class User<ActiveRecord::Base
...
...
before_create :create_remember_token
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = User.encrypt(User.new_remember_token)
end
这样做有什么区别:
def User.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
...
private
def create_remember_token
self.remember_token = User.encrypt(User.new_remember_token)
end
而且:
def encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = self.encrypt(self.new_remember_token)
end
如果没有差异,是否有一些东西使前者比后者更可取?
答案 0 :(得分:4)
在类定义(例如class User ...
)中,def encrypt ...
定义实例方法,而def self.encrypt ...
定义类方法
假设我们像这样定义了User类:
class User
def initialize(name)
@name_instance_variable = name
end
# This is an instance method definition, creating a method called `encrypt`
# on instances of the User class.
def encrypt
puts "Received User#encrypt instance method.\n" +
"- `self` is an instance of #{ self.class }.\n" +
"- `@name_instance_variable` is #{ @name_instance_variable.inspect }.\n"
end
# This is a class method definition, creating a method called `encrypt` on
# the User class itself.
def self.encrypt
puts "Received User.encrypt class method.\n" +
"- `self` is an instance of #{ self.class }.\n" +
"- `@name_instance_variable` is #{ @name_instance_variable.inspect }.\n"
end
end
现在我们可以使用构造函数User.new
创建User的实例,它会自动调用上面的initialize
:
my_user = User.new("Tim")
在initialize
中,我们为构造函数赋予的值被分配给实例变量 @name_instance_variable
。
现在我们可以在my_user
,我们的User类的实例上调用实例方法:
my_user.encrypt
# => Received User#encrypt instance method.
# - `self` is an instance of User.
# - `@name_instance_variable` is "Tim".
我们可以创建另一个实例并为构造函数赋予不同的值:
User.new("Jordan").encrypt
# => Received User#encrypt instance method.
# - `self` is an instance of User.
# - `@name_instance_variable` is "Jordan".
我们还可以在User 类上调用类方法:
User.encrypt
# => Received User.encrypt class method.
# - `self` is an instance of Class.
# - `@name_instance_variable` is nil.
我们nil
获得了@name_instance_variable
,因为类方法无法访问我们创建的任何实例,我们还没有'做了任何可以为其赋值的事情。
之后我们创建了User类,我们也可以定义一个新的类方法:
def User.create_remember_token
puts "Received User.create_remember_token class method."
# Now let's use the User.encrypt class method from within this method.
encrypt
# We could also write this, because inside this class method `self` refers
# to the User class itself:
#
# self.encrypt
#
# This is equivalent too--almost (the difference appears when you use
# inheritance, i.e. create a subclass of User, but that's beyond the scope
# of this answer):
#
# User.encrypt
#
end
......这相当于(但不一定是优选的):
class User
def self.create_remember_token
# ...
end
end
现在我们有一个User.create_remember_token
类方法,其工作方式如下:
User.create_remember_token
# => Received User.create_remember_token class method.
# Received User.encrypt class method.
# - `self` is an instance of Class.
# - `@name_instance_variable` is nil.
假设我们定义了另一个实例方法:
class User
# An instance method this time:
def create_remember_token
puts "Received User#create_remember_token instance method.\n"
"- `@name_instance_variable` is #{ @name_instance_variable }."
encrypt
# We could also write this, because inside this instance method `self`
# refers to this instance of User:
#
# self.encrypt
#
end
end
现在我们有一个User#create_remember_token
实例方法,它执行此操作:
my_user = User.new("Alice")
my_user.create_remember_token
# => Received User#create_remember_token instance method.
# - `@name_instance_variable` is "Alice."
# Received User#encrypt instance method.
# - `self` is an instance of User.
# - `@name_instance_variable` is "Alice".
最后,有两种方法可以从实例方法中调用类方法。假设我们定义了这样的实例方法:
class User
def create_remember_token
puts "Received User#create_remember_token instance method."
"- `@name_instance_variable` is #{ @name_instance_variable }."
# Since `self` refers to this instance of User, `self.class` equals
# the User class itself.
self.class.encrypt
# That's is (almost) equivalent to this:
#
# User.encrypt
#
# ("Almost" again because of the inheritance issue mentioned above.)
#
end
end
现在它会像这样工作:
User.new("Bob").create_remember_token
# => Received User#create_remember_token instance method.
# - `@name_instance_variable` is "Alice."
# Received User.encrypt class method.
# - `self` is an instance of Class.
# - `@name_instance_variable` is nil.
请注意,即使我们在实例方法User.encrypt
内调用类方法User#encrypt
,实例变量 @name_instance_variable
在类方法中仍然不可用。
那么为什么存在类方法呢?因为您并不总是使用实例。
在Rails中有很好的例子。假设我们有一个“评论”模型。 Comment#update_attributes
是实例方法,因为它适用于Comment的实例。它知道实例的属性及其内部状态。当您使用update_attributes
时,您知道您正在对该评论实例进行更改。
Comment.find
是类方法。当您使用Comment.find
时,您还没有评论实例。如果find
是实例方法,我们将无法在未先创建实例的情况下使用它:
id = 6
comment = Comment.new.find(id) # => #<Comment id: 6, ...>
......这没有多大意义。
Rails可以用另一种方式完成,比如定义一个CommentFinder类,这使得 little 更有意义:
comment_finder = CommentFinder.new
comment = comment_finder.find(id) # => #<Comment id: 6, ...>
...实际上在某些语言中很常见(甚至在一些Ruby库中;流行的factory pattern也这样做)。但是在Rails中,他们决定通过使这个功能成为Comment类的类方法来使这更简单,所以我们可以这样做:
comment = Comment.find(id) # => #<Comment id: 6, ...>
您可能已经注意到我一直在引用实例方法,如下所示:User#encode
和类方法,如下所示:{{1} }。这是一个您将在Ruby和Rails的文档以及关于这两者的书籍和文章中看到的约定。类名后面的User.encode
表示它是一个实例方法,而#
表示它是一个类方法。
Ruby是一种“消息传递”语言。你真的不需要担心这种区别,但是由于你可能会遇到一些术语,因此知道这一点很方便。当我们写.
时,我们在技术上将my_user.encode_video
encode_video
对象。 my_user
收到邮件my_user
。
通俗地说,我们经常说“我调用 encode_video
方法”或“此代码调用 encode
”,并且可以使用相反,这些术语(实际上更多的开发人员可能会理解它们)。你可能会在很长一段时间内找到代码,但这样做会像encode
,甚至是my_user.send(:encode_video)
这样,当你理解“发送”意味着“调用”或“调用”时更有意义。 / p>
Ruby是一种非常灵活的语言。在我写的一些输出中,“type = "video"; ... my_user.send("encode_#{type}")
是Class的实例”。这不是一个错误 - 在Ruby中,即使类是对象,您创建的任何类都将是名为Class的类的实例。甚至有一些方法可以在类本身(而不是它的实例)上定义实例方法和实例变量,但这超出了本答案的范围。如果你因好奇而死,请点击“eigenclass。”
我希望这有用。
答案 1 :(得分:2)
编辑 - 要回答最初的问题,def ClassName.method
是类的显式声明,def self.method
是一个类方法,引用该方法在类中定义的类。我想不出ClassName.method
的用法,但我确信在广泛的元编程范围内有一个地方。
区别在于
def encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
是实例方法。它只能在类(对象)的实例上调用。
def User.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
是类方法。它只能在课堂上调用。
在实例方法中,self
表示实例,ClassName
将始终引用该类。在类方法中,self
指的是类。
差异的原因是设计模式。在这种情况下,因为该方法从不使用User
类的实例,所以最好留作类方法。
答案 2 :(得分:0)
我相信self.method和Class.method是等价的。显然,如果你想在多个类之间混合代码,你需要使用self。否则我认为它们在所有意图和目的上是相同的。