Ruby的面向对象方式编程学习杂记

打开类

可以重新打开已经存在的类并对之进行动态修改,即使像String或者Array这样标准库的类也不例外。这种行为方式称之为打开类(open class)

猴子补丁

如果你粗心地为某个类添加了新功能,同时覆盖了类原来的功能,进而影响到其他部分的代码,这样的patch称之为猴子补丁(Monkeypatch)

类与模块

Ruby的class关键字更像是一个作用域操作符,而不是类型声明语句。class关键字的核心任务是把你带到类的上下文中,让你可以在里面定义方法。

每个类都是一个模块,类就是带有三个方法(new,allocate,superclass)的增强模块,通过这三个方法可以组织类的继承结构,并创建对象

Ruby中的类和模块的概念十分接近,完全可以将二者相互替代,之所以同时保留二者的原因是为了保持代码的清晰性,让代码意图更加明确。使用原则:

  • 希望把自己代码包含(include)到别的代码中,应该使用模块
  • 希望某段代码被实例化或被继承,应该使用类
  • 模块机制可以用来实现类似其它语言中的命名空间(Namespace)概念

Ruby中的::符号

Ruby中常量的路径(作用域),类似与文件系统中的目录,通过::进行分割和访问,默认直接以::开头(例: :: Y)表示变量路径的根位置

什么是对象

对象就是一组实例变量外加一个指向其类的引用。对象的方法并不存在于对象本身,而是存在于对象的类中。

什么是类

类就是一个对象(Class类的一个实例)外加一组实例方法和一个对其超类的引用。Class类是Module类的子类,因此一个类也是一个模块。

load与require方法的异同

通过load和require都可以进行导入别人的代码,不同的是load方法用来加载代码,如果不希望污染当前的命名空间,需要通过load(‘file.rb',true)显式的要求创建一个匿名模块来,接管file.rb的常量,require用于导入类库,此外,就加载次数上load方法每次调用都会再次运行所加载文件,require则对每个库文件只加载一次。

prepend、include与祖先链

祖先链用于描述Ruby对象的继承关系,因为类与模块是父子关系,所以祖先链中也可以包含模块,prepend与include分别可以向链中添加模块,不同的是调用include方法,模块会被插入祖先链,当前类的正上方,而prepend同样是插入到祖先链,但位置其他却在当前类的正下方,另外通过Class.ancestors可以查看当前的祖先链

private规则

不能通过明确指定接受者来调用私有方法。私有方法只能通过隐性的接受者self调用(Object#send是个例外)

self相关

调用一个方法时,接受者会扮演self角色 任何没有明确指定接受者的方法调用,都当做是调用self的方法 定义一个模块(或类)时,该模块(或类)会扮演self角色

对象、类与模块之间关系

上面Module.class指向的也是Class类,可以理解为上面方框内容均为Class,但他们的父子组织关系通过superclass建立并存在异同,可以通过Class.ancestors查看。

动态方法

动态调用方法

在Ruby中通过Object#send方法可以代替点标识调用对象的指定实例方法

示例代码

class MyClass
  def my_method(my_arg)
    my_arg * 2
  end
end

obj = MyClass.new
obj.my_method(3)  #=> 6
obj.send(:my_method, 3) #=> 6

上面代码通过直接调用和使用send方法调用得到的结果是一样的,使用send的好处是,可以在编码中,动态的决定方法调用。这个技巧在元编程中被称为动态派发

另外需要指出的地方是通过Object#send不仅可以调用公共方法,也可以调用对象的私有方法。如果想保留对象的封装特性,不向外暴露私有方法可以使用Object#public_send方法。

动态定义方法

除了方法的动态调用之外,Ruby还通过Module#define_method方法和代码块提供了动态方法定义方式

示例代码

class MyClass
  define_method :my_method do |my_arg|
    my_arg * 3
  do
end

obj = MyClass.new
obj.my_method(2) #=> 6

上面代码通过define_method方法取代了关键词def,其本质上都是相同的,只是在定义方式上,define_method的方式更加灵活一些,可以通过在编码中通过推导,完成函数的定义,增加了实现的灵活性。

method_missing方法

严格意义上将method_missing方法,并不算是明确的定义(不会出现在methods列表中),其本质是通过方法查找的机制来截获调用信息进而合理的给出相应方法的回应。有点类似与异常处理中的抛出异常,一层一层的往外抛。

method_missing利用的机制是,当一个对象进行某个方法调用的时候,会到其对应的类的实例方法中进行查找,如果没有找到,则顺着祖先链向上查找,直到找到BasicObject类为止。如果都没有则会最终调用一个BasicObject#method_missing抛出NoMethodError异常。

当我们需要定义很多相似的方法时候,可以通过重写method_missing方法,对相似的方法进行统一做出回应,这样一来其行为就类似与调用定义过的方法一样。

示例代码

class Roulette
 def method_missing(name, *args)
  person = name.to_s.capitalize
  super unless %w[Bob Frank Bill Honda Eric].include? person
  number = 0
  3.times do
   number = rand(10) + 1
   puts "#{number}..."
  end
  "#{person} got a #{number}"
 end
end

number_of = Roulette.new
puts number_of.bob
puts number_of.kitty

动态代理

对一些封装过的对象,通过method_missing方法收集调用,并把这些调用转发到被封装的对象,这一过程称为动态代理,其中method_missing体现了动态,转发体现了代理

const_missing方法

与method_missing类似,还有关于常量的const_missing方法,当引用一个不存在的常量时,Ruby会把这个常量名作为一个符号传递给const_missing方法。

白板类(blank slates)

拥有极少方法的类称为白板类,通过继承BasicObject类,可以迅速的得到一个白板类。除了这种方法以外,还可以通过删除方法来将一个普通类变为白板类。

删除方法

删除某个方法有两种方式:

  • Module#undef_method
  • Module#remove_method

二者的区别是Module#undef_method会删除所有(包括继承而来的)方法。而Module#remove_method只删除接受者自己的方法,而保留继承来的方法。

动态方法与Method_missing的使用原则

当可以使用动态方法时候,尽量使用动态方法。除非必须使用method_missing方法(方法特别多的情况),否则尽量少使用它。

(0)

相关推荐

  • ruby 学习笔记(2) 类的基本使用

    ruby语言跟c#的一些重要差别在于: 1.ruby是动态语言,c#是静态语言--即对象在new出来以后,ruby还可以动态给对象实例添加一些属性或方法(javascript也是如此) 2.ruby中刻意弱化了变量类型这个概念,默认情况下变量/方法都不需要声明具体(返回)类型,但其实在ruby内部,会自动根据变量的值分配类型.(可以通过 "puts 变量.class"查看) 3.ruby相对c#来讲,可能有些雷的地方在于:父类中的private成员,居然是可以在子类中使用的! ...其

  • 简要解读Ruby面向对象编程中的作用域

    作用域 Ruby中不具备嵌套作用域(即在内部作用域,可以看到外部作用域的)的特点,它的作用域是截然分开的,一旦进入一个新的作用域,原先的绑定会被替换为一组新的绑定. 程序会在三个地方关闭前一个作用域,同时打开一个新的作用域,它们是: 类定义class 模块定义 module 方法定义 def 上面三个关键字,每个关键字对应一个作用域门(进入),相应的end则对应离开这道门. 扁平化作用域 从一个作用域进入另一个作用域的时候,局部变量会立即失效,为了让局部变量持续有效,可以通过规避关键字的方式,使

  • Ruby 中$开头的全局变量、内部变量、隐藏变量介绍

    Ruby 中充满了一系列的隐藏变量,我们可以从这些预定义的全局变量中获取一些有意思的信息. 全局进程变量 $$ 表示当前运行的 ruby 进程. 复制代码 代码如下: >> $$=> 17170 我们可以从当前进程杀死它自己 复制代码 代码如下: >> `kill -9 #{$$}`[1]    17170 killed     irb $? 表示最近一个子进程的状态 复制代码 代码如下: >> `echo hello`=> "hello\n&qu

  • Ruby面向对象编程详解

    Ruby是纯面向对象的语言,所有项目似乎要Ruby中为一个对象.Ruby中的每个值是一个对象,即使是最原始的东西:字符串,数字甚至true和false.即使是一个类本身是一个对象,它是Class类的一个实例.本章将通过所有功能涉及到Ruby的面向对象. 类是用来指定对象的形式,它结合了数据表示和方法操纵这些数据,转换成一个整齐的包.在一个类的数据和方法,被称为类的成员. Ruby类的定义: 定义一个类,定义的数据类型的草图. 这实际上并不定义任何数据,但它定义的类名字的意思什么,即是什么类的对象

  • ruby 类常量 解析

    一个常量由大写字母开头.它应最多被赋值一次.在Ruby的当前版本中,常量的再赋值只会产生警告而不是错误(non-ANSI版的eval.rb不会报告这一警告) ruby>fluid=30    30 ruby>fluid=31    31 ruby>Solid=32    32 ruby>Solid=33    (eval):1: warning: already initialized constant Solid    33 常量可以定义在类里,但不像实变量,它们可以在类的外部访

  • ruby 变量

    Ruby有三类变量,一种常量和两种严格意义上的伪变量(pseudo-variables).变量和常量都没有类型.虽然无类型变量存在一定的缺点,但却有更多的优点并很好的符合Ruby快速简便(quick and easy)的哲学精神. 在大多数语言里,变量都必须指定其类型,可更改性(是不是个常数)和范围;由于类型的不存在,剩下的东西也可由变量名字很快确定(你马上会看见),在Ruby里我们不需要变量声明. 由首字母标识符将其分类: $          全局变量   @          实变量  [

  • 解析 ruby 全局变量

    全局变量由$开头.它们可以在程序的任何位置访问到.在初始化前,全局变量有一个特殊的值 nil. ruby> $foo    nil ruby> $foo = 5    5 ruby> $foo    5 应谨慎使用全局变量.由于在任何地方都可以被写因此他们相当危险.滥用全局变量会导致很难隔离臭虫;同时也视为程序的设计未经严格考虑.当你发现必须要使用全局变量时,记得给它一个不会在其它地方一不小心就用到的描述性名字(像上面那样叫$foo可能不是一个好想法). 全局变量的好处是其可以被跟踪;你

  • ruby 面向对象思维 概念

    面向对象是一个挺让人迷惑的措辞.叫一切东西都是面向对象会让别人觉得你很时髦. Ruby 声称自己是面向对象的脚本语言;但究竟什么才是"面向对象"? 我们已经有了各种各样的答案,但所有这些恐怕都归结于同一件事.与其快速地概括它,不如让我们先花点儿时间考虑一下传统的编程模式. 传统意义上,一个编程问题从出现的各种数据,以及处理数据的过程(procedures)着手.在这一模式下,数据是呆板,被动和无用的;它完全的求助于那个体积庞大的,主动的,逻辑性的,全能的过程体. 这一做法的问题在于程序

  • ruby 局部变量

    局部变量由小写字母或下划线(_)开头.局部变量不像全局和实变量一样在初始化前含nil值. ruby> $foo    nil ruby> @foo    nil ruby> foo ERR: (eval):1: undefined local variable or method `foo' for main(Object) 对局部变量的第一次赋值做的很像一次声明.如果你指向一个未初始化的局部变量,Ruby解释器会认为那是一个方法的名字;正如上面所见错误 信息的. 一般的,局部变量的范围

  • Ruby的面向对象编程的基础教程

    Ruby 是纯面向对象的语言,Ruby 中的一切都是以对象的形式出现.Ruby 中的每个值都是一个对象,即使是最原始的东西:字符串.数字,甚至连 true 和 false 都是对象.类本身也是一个对象,是 Class 类的一个实例.本章将向您讲解所有与 Ruby 面向对象相关的主要功能. 类用于指定对象的形式,它结合了数据表示法和方法,把数据整理成一个整齐的包.类中的数据和方法被称为类的成员. Ruby 类定义 当您定义一个类时,您实际是定义了一个数据类型的蓝图.这实际上并没有定义任何的数据,而

随机推荐