开篇
上回说到,在Ruby语言中,万物皆为对象。
类class和模块module也不例外,也是对象,只不过方法和用途各有不同。
本回综合的看一下class跟module的一些故事。
Ruby的类,就是一段被执行的代码
如果你用过Java等静态OO语言,当你定义一个类的时候,你是在声明一个数据类型。
在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
- 定义一个常量,就是class后面的名字Test
- 生成一个新的Class的实例对象并赋值给第一步里面生成的常量
- 把self的值换成第二步生成的Class实例对象
以上完成之后,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的类,就是一个普通的对象
上一个部分说了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.
类的self和类的方法
类有两种方法,一是实例方法,例如:
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
module跟class是绝代双骄,因为他们都是对象,而且有很多相似指出。
其实说法不太对。
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: zyinghai@gmail.com
weibo: http://www.weibo.com/zyinghai
twitter: https://twitter.com/zyinghai
github: https://github.com/hisea
Hisea.me 版权所有