注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

世界的瞭望哨

认识自己 认识世界

 
 
 

日志

 
 

Zz Ruby和元编程的故事 - 第2回: 类与模块,Ruby的绝代双骄  

2012-12-01 12:33:30|  分类: Ruby |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
原文传送门:
http://hisea.me/p/ruby-story-ep2

尘泥:
看了这篇文章后才决定找本书学下元编程

=====================================

开篇

上回说到,在Ruby语言中,万物皆为对象

类class和模块module也不例外,也是对象,只不过方法和用途各有不同。

本回综合的看一下class跟module的一些故事。

Ruby的类,就是一段被执行的代码(尘泥:言简意赅的一句话)

如果你用过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

  1. 定义一个常量,就是class后面的名字Test
  2. 生成一个新的Class的实例对象并赋值给第一步里面生成的常量
  3. 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是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: zyinghai@gmail.com
weibo: http://www.weibo.com/zyinghai
twitter: https://twitter.com/zyinghai
github: https://github.com/hisea

Hisea.me 版权所有

  评论这张
 
阅读(341)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017