Ruby元编程-Week-4
2016-01-09 14:20
447 查看
更多文章欢迎来沈小黑的菜园转转啦啦啦~~
另外,class是一个作用域门,类外的变量对类里面来说是不可见的。而class_eval则使用扁平作用域,因为它归根结底只是一个方法调用。
可以发现class_eval和instance_eval很相似,class_eval也有带参数版本class_exec。instance_eval的重点在于修改对象中变量的当前self,使其在调用处可见,而class_eval的重点在于修改类。
正因为实例变量的归属根据当前self判断,所以子类、实例都不能访问类实例变量,因为这个变量属于“类”这个对象的self。
注意和@@开头的类变量区别开来,它是可以被当前类和子类或实例访问的。正是因为这样,在顶级作用域定义类变量时,所有类都可以访问到它,因为它们都在顶级作用域的覆盖下。
为了避免这样的情况,可以的话最好避免使用类变量,转而使用类的实例变量。
和Java中singleton类只允许生成同一个对象的概念有所不同,这里的singleton方法是说这个方法只属于定义它的对象。
实际上,联想到定义类方法时的方法名前的self,类方法其实就是Class对象的单件方法罢了。
但是这样写很烦,一点也不酷~ 所以Ruby提供了Module#attr_reader、Module#attr_writer和Module#attr_accessor
类似attr_accessor这样的方法就叫做类宏,他们看起来像是关键字,实际上只是可以用在类里的方法。
假设需要更新API,将旧的方法声明为deprecated,并且自动转到新方法,那么可以使用像下面这样的代码:
这样我们就可以看到这个单件类的引用(.class方法会包装隐藏单件类),现在Ruby也提供.singleton_class方法返回单件类。每个对象都有自己的单件类,单件类也只有一个实例(有实例说明它的实例也有自己的单件类~循环往复……只是这个单件类的单件类的用途还没有被开发过)。对象的单件方法就放在对应的单件类中。
现在需要更新一下在星期一学习的继承模型了:obj所在的类是obj的单件类,单件类的父类才是MyClass,MyClass类对象的父类是Object。
很少会这样用,但这样是可以的。instance_eval的标准含义还是:修改self作用域内容。
因为如果在单件类中定义了方法,这些方法实际上就会成为类方法。而属性其实就是一对方法,所以这样就达到了类属性的效果。
除了使用打开类/对象,还可以使用简单的Object#extend方法。
如果原方法在设置了别名之后又被修改了,那么别名指向的还是未被修改时的代码。
但是这种方法有猴子补丁的问题:所有地方的这个方法都被改变了,但是也许我们只需要在某些地方包装这个方法。
当Foo中声明了 refinement 时,我们可以看出它可以使用直接camelize了,但在Foo外却无法使用它。
在refine中使用super可以向方法中包装额外的功能。
这样可以确保只在需要这个特性的时候才用到,不会在任何地方都曲解String#length的意思导致混乱。
这一种方法最为清晰,既避免了猴子补丁,也不用考虑细化的问题。细化会导致很多问题,具体可以看这篇文章,大意是原来的方法调用可以做到缓存来静态查找,但是现在refine让方法查找必须动态。更糟糕的是,任何一个块被调用的时候都有可能被refine了,这是很恐怖的。
这章就到这里吧~~
类定义
即将走进”Ruby对象模型最深暗的角落”…和Java不同,在Ruby中,定义类实际上就是在运行代码,这种思想催生了1、可以修改类的类宏 2、可以在其他方法前后封装额外代码的环绕别名。当然,因为类不过是增强的模块,所以这些知识也可以应用于模块。当前类
方法所属判断是依靠定义方法的所在self判断的,所以对于父类定义的方法,即使在子类中运行使得m2定义语句得到运行,m2依然属于父类。class C def m1 def m2 end end end p C.instance_methods(false) # => [:m1] class D<C end D.new.m1 p C.instance_methods(false) # => [:m1, :m2] p D.instance_methods(false) # => []
class_eval V.S class关键字
使用class关键字修改类需要指定类名才可以,那假如想要将类名作为参数来动态修改某个类呢?这时候可以用到Module#class_eval(别名为module_eval)方法。def add_method_to(a_class) a_class.class_eval do def m 'Hello!' end end end add_method_to String p "abc".m # => "Hello!"
另外,class是一个作用域门,类外的变量对类里面来说是不可见的。而class_eval则使用扁平作用域,因为它归根结底只是一个方法调用。
可以发现class_eval和instance_eval很相似,class_eval也有带参数版本class_exec。instance_eval的重点在于修改对象中变量的当前self,使其在调用处可见,而class_eval的重点在于修改类。
类的实例变量
Ruby的实例变量属于当前self,所以在类中的方法外定义的实例变量属于类这个对象而不是类的实例。class A @var = 1 def read; @var; end def write; @var = 2; end def self.read; @var; end end class B<A def self.read; @var; end end obj = A.new p obj.read # => nil p B.read # => nil obj.write p obj.read # => 2 p A.read # => 1
正因为实例变量的归属根据当前self判断,所以子类、实例都不能访问类实例变量,因为这个变量属于“类”这个对象的self。
注意和@@开头的类变量区别开来,它是可以被当前类和子类或实例访问的。正是因为这样,在顶级作用域定义类变量时,所有类都可以访问到它,因为它们都在顶级作用域的覆盖下。
@@var = 1 class A @@var = 2 end @@var = 2
为了避免这样的情况,可以的话最好避免使用类变量,转而使用类的实例变量。
单件方法
Ruby支持给单个对象添加方法:str = "this is a string" def str.title? self.upcase == self end str.singleton_methods # => [:title?]
和Java中singleton类只允许生成同一个对象的概念有所不同,这里的singleton方法是说这个方法只属于定义它的对象。
实际上,联想到定义类方法时的方法名前的self,类方法其实就是Class对象的单件方法罢了。
类宏
Ruby对象没有属性,所以需要写类似Java的getter和setter才能访问或修改属性。class MyClass def my_attribute=(value) @my_attribute = value end def my_attribue @my_attribute end end obj = MyClass.new obj.my_attribute = 1 obj.my_attribute # => 1
但是这样写很烦,一点也不酷~ 所以Ruby提供了Module#attr_reader、Module#attr_writer和Module#attr_accessor
class MyClass attr_accessor :my_attribute end obj = MyClass.new obj.my_attribute = 1 obj.my_attribute # => 1
类似attr_accessor这样的方法就叫做类宏,他们看起来像是关键字,实际上只是可以用在类里的方法。
假设需要更新API,将旧的方法声明为deprecated,并且自动转到新方法,那么可以使用像下面这样的代码:
class MyClass def self.deprecate(old_method, new_method) define_method(old_method) do |*args, &block| warn "Warinig, #{old_method} is deprecated, use #{new_method} now!" send(new_method,*args,&block) end end def old_m p "old" end def new_m p "new" end deprecate :old_m, :new_m end obj = MyClass.new obj.old_m
单件类
Ruby查找方法时先进入接收者的类,然后再向上查找。比如一个MyClass类实例obj调用方法,就会进入obj的类MyClass来查找方法。但是单件方法肯定不在类里,否则所有对象都有这个方法了。而对象本身又不是一个类,所以也不在obj里。那么这个方法在哪里呢?什么是单件类
当询问对象的类时,Ruby返回的其实不是看到的类,而是对象特有的隐藏类。这个类被称为这个对象的单件类,或者叫元类。obj = Object.new singleton_class = class << obj self # => #<Class:#<Object:0x007fcc711e4278>> end p singleton_class
这样我们就可以看到这个单件类的引用(.class方法会包装隐藏单件类),现在Ruby也提供.singleton_class方法返回单件类。每个对象都有自己的单件类,单件类也只有一个实例(有实例说明它的实例也有自己的单件类~循环往复……只是这个单件类的单件类的用途还没有被开发过)。对象的单件方法就放在对应的单件类中。
现在需要更新一下在星期一学习的继承模型了:obj所在的类是obj的单件类,单件类的父类才是MyClass,MyClass类对象的父类是Object。
单件类和继承
假设有如下的类关系,我们可以通过观察继承树输出来了解他们的结构关系class C def self.claz_method end end class D<C end obj = D.new def obj.singleton_mtd end p C.singleton_class # => #<Class:C> p D.singleton_class # => #<Class:D> p D.singleton_class.superclass # => #<Class:C> p C.singleton_class.superclass # => #<Class:Object> p BasicObject.singleton_class.superclass # => Class
instance_eval和单件类的关系
instance_eval方法会把当前类修改为接收者的单件类。s1 = 'abc' s1.instance_eval do def swoosh!; reverse; end end
很少会这样用,但这样是可以的。instance_eval的标准含义还是:修改self作用域内容。
类属性
如果想给类创建属性的话,可以使用下面这样的形式:class MyClass class << self attr_accessor :c end end
因为如果在单件类中定义了方法,这些方法实际上就会成为类方法。而属性其实就是一对方法,所以这样就达到了类属性的效果。
类扩展/对象扩展
如果想要从一个Module中导入一个方法到类/对象中,可以打开这个类/对象,然后通过include模块来扩展方法。module MyModule def my_method; 'hello'; end end class MyClass class << self include MyModule end end MyClass.my_method #完成了类方法的扩展 obj = Object.new class << obj include MyModule end obj.my_method #完成了对象方法的扩展 obj.singleton_methods # => [:my_method]
除了使用打开类/对象,还可以使用简单的Object#extend方法。
module MyModule def my_method; 'hello'; end end obj = Object.new obj.extend MyModule obj.my_method # => 'hello' class MyClass extend MyModule end MyClass.my_method # => 'hello'
方法包装器
如果一个不能修改的接口(比如在库中)有很多地方都被使用,而又想要在所有使用这个接口的地方增加新功能,那找到所有使用接口的地方逐条修改就会变成一件痛苦的事情。Ruby为我们提供了方法包装器的技巧来解决这个问题。方法别名
使用alias可以为Ruby方法起别名,只要不在顶级作用域里面,可以使用Module#alias_method方法,而在顶级作用域里面,只能使用alias关键字。class MyClass def my_method; 'my_method()'; end alias_method :m, :my_method end obj = MyClass.new obj.my_method # => 'my_method()' obj.m # => 'my_method' def my_method2 'hello' end alias :m2 :my_method2 m2 # => 'hello'
如果原方法在设置了别名之后又被修改了,那么别名指向的还是未被修改时的代码。
class String alias_method :real_length, :length def length real_length > 5 ? 'long' : 'short' end end "War and Peace".length # => "long" "War and Peace".real_length # => 13
不同的方法包装器
环绕别名
我们可以通过为A方法设置一个别名B,然后重新定义A方法,插入新添加的功能,最后在A中调用B方法(别名了的原A方法,利用了上面说到的“指向未被修改时的代码”的特性)。但是这种方法有猴子补丁的问题:所有地方的这个方法都被改变了,但是也许我们只需要在某些地方包装这个方法。
细化
refinements 提供一种方法让类的修改只影响某个作用域,而可以避免猴子补丁那样影响全局的问题。看下面的代码,我添加一个camelize方法到String类中,但它只能被Foo使用。module Camelize refine String do def camelize dup.gsub(/_([a-z])/) { $1.upcase } end end end class Foo using Camelize def camelize_string(str) str.camelize end end Foo.new.camelize_string('blah_blah_blah') # => "blahBlahBlah" 'blah_blah_blah'.camelize # => NoMethodError
当Foo中声明了 refinement 时,我们可以看出它可以使用直接camelize了,但在Foo外却无法使用它。
在refine中使用super可以向方法中包装额外的功能。
module StringRefinement refine String do def length super > 5 ? 'long' : 'short' end end end using StringRefinement "War and Peace".length # => "long"
这样可以确保只在需要这个特性的时候才用到,不会在任何地方都曲解String#length的意思导致混乱。
Module#prepend包装
Module#prepend的作用和include类似,只是它把包含的模块插在祖先链的下方,这样的话寻找方法时会先找到被包含的模块中的方法,同时可以通过super调用该类中的原始方法。module ExplicitString def length super > 5 ? 'long' : 'short' end end String class_eval do prepend ExplicitString end "War and Peace".length # => 'long'
这一种方法最为清晰,既避免了猴子补丁,也不用考虑细化的问题。细化会导致很多问题,具体可以看这篇文章,大意是原来的方法调用可以做到缓存来静态查找,但是现在refine让方法查找必须动态。更糟糕的是,任何一个块被调用的时候都有可能被refine了,这是很恐怖的。
这章就到这里吧~~
相关文章推荐
- Ruby元编程-Week-3
- 有趣的Ruby-学习笔记4
- Ruby元编程-Week-2
- Ruby元编程-Week-1
- Ruby学习之概述
- Ruby on rails 连接mysql数据库实践
- Ruby on Rails环境中的异步编程
- 如何在Mac OS X上安装 Ruby运行环境
- 使用druby,达到ruby的子进程向父进程传递子进程PID数据
- ruby如何打log
- RubyGems 镜像
- iOS Mac系统下Ruby环境安装
- MAC机中安装RUBY环境
- Ruby总结
- 在Mac OS X下安装Ruby环境
- Ruby语法十分钟
- /Library/Ruby/Gems/2.0.0/gems/cocoapods-stats-0.5.3/lib/cocoapods_plugin.rb 解决方法
- Mac机中安装RUBY环境,CocoaPods安装和使用教程(★firecat推荐★)
- VB.NET和C# Ruby语言之间的转换
- [Ruby]Unzipping a file using rubyzip