广告:http://hisea.me/p/ruby-story-ep2
上回说到,在 Ruby 语言中,万物皆为对象。
类 class 和模块 module 也不例外,也是对象,只不过方法和用途各有不同。
本回综合的看一下 class 跟 module 的一些故事。
如果你用过 Java 等静态 OO 语言,当你定义一个类的时候,你是在声明一个数据类型。
public class Test {
}
在 Ruby 中,并不存在这个声明的过程,类的定义与其他的代码没有什么不同,完全就是在执行一段代码。
例如 >> class Test >> puts "hello world" >> end hello world => nil
如果不是在声明定义,完全是在执行代码,那么 class 这个 key word 到底起到什么作用呢?
第一回中我们说,self 在 Ruby 中是个重要的概念。
self 是一个特殊的变量,里面保存的是当前的对象,方法的调用/定义,实例变量的解析,接受者都会默认为 self 对象。self 保存的值不能被显式的修改,只有通过特殊途径可以修改 self 的值。一个是(.)这个符号,当我们调用一个方法时,例如 obj.method() 之后 method 的解析和运行就会把 self 的值切换到 obj。
另外一个能修改 self 值的途径,就是class关键字。
class 能够达到下面几个作用,比如我们有前面的 class Test
以上完成之后,Ruby 继续执行 class 内的代码,直到 end.
实际上,如果已经存在一个同名常量,Ruby 会重新使用那个常量,如果常量不是 class 就会报错 >> Test = 2 => 2 >> class Test >> end TypeError: Test is not a class from (irb):2
如果是已存在的类,class 会把 self 转到那个类对象,继续执行后面的代码,包括重新定义方法的代码。 这个行为看上去就像是打开类重新定义新内容,Monkey Patching 就是利用了这个特性。
上一个部分说了 Ruby 的类就是在不同的上下文环境 (class 的 self) 中执行一段代码。
这个 self 指向的,class 本身,其实就是一个很简单的对象,这个对象是 Class 类的一个实例,当然 Class 类也是一个对象。
我们完全可以不用 class 关键字来定义类。
我们可以用Class.new
来生成一个类的对象赋值给一个常量。这样生成的类跟 class 关键字生成的一样好用。
>> Test = Class.new
=> Test
从这里可看出来,Test 就是 Class 类的一个对象而已,这个对象有 Class 的实例方法。
>> Class.instance_methods => ["private_class_method", "inspect", "name", "tap", "clone", "public_methods", "send", "method_defined?", "instance_variable_defined?", "yaml_tag_read_class", "autoload", "equal?", "freeze", "extend", "send", "const_defined?", "methods", "to_yaml_properties", "ancestors", "module_eval", "hash", "dup", "object_id", "instance_methods", "public_method_defined?", "yaml_as", "instance_variables", "class_variable_defined?", "eql?", "constants", "id", "instance_eval", "singleton_methods", "module_exec", "instance_method", "const_missing", "taint", "autoload?", "instance_variable_get", "frozen?", "to_enum", "private_method_defined?", "public_instance_methods", "display", "instance_of?", "superclass", "to_a", "included_modules", "const_get", "instance_exec", "type", "<", "protected_methods", "<=>", "class_eval", "==", "class_variables", ">", "===", "instance_variable_set", "enum_for", "protected_instance_methods", "protected_method_defined?", "yaml_tag_class_name", "taguri", "respond_to?", "kind_of?", ">=", "method", "public_class_method", "to_s", "<=", "const_set", "allocate", "taguri=", "class", "new", "private_methods", "=~", "tainted?", "id", "class_exec", "untaint", "nil?", "private_instance_methods", "to_yaml", "to_yaml_style", "include?", "is_a?"]
这些实力方法中,有些是大家比较熟悉的,例如#class, #superclass,最关键的是,有一个方法叫做new.
就是说 Class 的实例对象 Test,有一个实例方法叫做 new,这个方法可以生成一个 Test 自己的实例对象。
>> t = Test.new => #Test:0x105e026c0
如果想往这个类上定义实例方法,可以用 Monkey Patch 的办法再次打开类,也可以用 class_eval.
类有两种方法,一是实例方法,例如:
class Test
def hello
puts "hello"
end
end
这里定义的是一个实例方法,只有通过 Test 的实例才能调用这个方法。如Test.new.hello
另一种是类方法,类方法就是类似这样的方法: Test.hello 看上去是不是很像 Test.new, 我们定义这一类型的方法时其实就是利用的影子类为 Test 对象创造了一个单例方法。 最简单的办法就是
>> Test = Class.new => Test >> def Test.hello >> puts "hello" >> end => nil >> Test.hello hello => nil
上面的例子也能显示 Test 其实就是一个简单的对象,hello 只不过是这个对象的单例方法,关于单例方法,详见第一回
大部分时候,类方法的定义是在类的内部。
class Test
def self.class_method1
puts "hello1"
end
def Test.class_method2
puts "hello2"
end
end
以上时两种在类的内部定义类方法的途径。 class_method2 的定义途径跟前面一个例子的一样,利用了 class 内部只是在执行代码这一特性。 这部分代码在类的内部和外部执行都是一个结果,给类对象添加一个 singleton method.
而 class_method1 的定义途径只是利用了 self 在 class 的内部及方法定义外部的时候,其值就是类对象本身。简单的说就是 self 的值就是 Test,所以两个定义方法是等价的。
除非在实例方法定义内,self 是实例对象,self 在类的内部其他部位就是其本身。
加上前面一个特性,类就是一段被执行的代码,就变的非常的强大了。
这个两个 特性被广泛的应用。例如我们熟悉的 has_many
class Post << ActiveRecord::Base
has_many :post
end
has_many 是一个方法,这个方法的接受者 receiver 是默认的 self,也就是类本身,换句话说,has_many 这个方法是个类方法。我们这里在类的内部调用了一个类方法。
我们可以用一个非常简单的例子来看 has_many 是怎么实现的:
>> Test = Class.new => Test >> def Test.has_many(name) >> puts "Rails style has_many #{name}" >> end => nil >> class Test >> has_many "photos" >> end Rails style has_many photos => nil >>
这里如果我们把 Test.has_many 类方法内的代码,换成能动态生成一系列方法,能 include 外部的 module,或者其他元编程的技巧,就达到了类似 Rails 中 ActiveRecord 的效果。这些元编程的具体技巧我们以后在研究。
module 跟 class 是绝代双骄,是因为他们都是对象,而且有很多相似指出。
其实说法不太对。
Module 是 Class 的父类:
Class.superclass => Module
module 没有实例变量
module 没有 new 不能生成实例对象
module 内可以有常量
>> module Test >> PI=3.14 >> end => 3.14 >> Test.PI >> Test::PI => 3.14
module 的方法有两种,一种是 module 方法,这类方法可以直接调用。
>> module Test >> def Test.test_method >> puts "hello from module" >> end >> end => nil >> Test::test_method hello from module => nil
另一种是没有 module 名字的方法,这种方法不能直接调用,需要 mixin 到一个类中。
>> module Test >> def hello >> puts "hello" >> end >> end => nil >> Test::hello NoMethodError: undefined method `hello' for Test:Module from (irb):23
把 module 的方法添加到类中有两种方法。
一种是 include,方法会被添加到实例方法中。
一种是 extend,方法会被添加到类方法中。
继续前面的 module Test 的例子
>> class Class1 >> include Test >> end => Class1 >> Class1.new.hello hello => nil >> class Class2 >> extend Test >> end => Class2 >> Class2.hello hello => nil
module 常用的一个 hook/callback 是 included 方法,这个方法在 module 被 include 到一个类中的时候会被调用。
>> module Test >> def self.included(cls) >> puts "including module in class #{cls.name}" >> end >> end => nil >> class Class1 >> include Test >> end including module in class Class1 => Class1
本回讲了 class 跟 module 的一些东西。
本文的例子非常非常的简单,不过很多东西结合起来使用就会很强大,
例如,我们可以定义一个 module Test,include Test 的时候 extend 另外一个 module Test::ClassMethods,这时就会给当前类添加很多类方法。
这些类方法又可以来定义跟生成一部分功能。达到类似下面的效果:
class Artist
include Mongoid::Document
field :name, type: String
embeds_many :instruments
end
如果你感兴趣,可以考虑一下 mongoid 怎么能实现上面的功能。
还没想好下回讲什么,或者是 eval 系列,或者是 define_method,或者是 block/lambda/Proc.new
如果你有任何问题,欢迎讨论。
作者:Hisea
web: http://hisea.me
email: [email protected]
weibo: http://www.weibo.com/zyinghai
twitter: https://twitter.com/zyinghai
github: https://github.com/hisea
Hisea.me 版权所有