Rubyist-c2-Objects methods and local variables
30 Sep 2017
注:这一系列文章是《The Well-Grounded Rubyist》的学习笔记。
But throughout, writing a Ruby program is largely a matter of engineering your objects so that each object plays a clear role and can perform actions related to that role.
我们对 object 所作的,总体上可以归为 1 向object 发出一段信息; 2 让object 执行某个动作。
写ruby程式更像是一场策划,导演。把合适的角色分配给合适的 object , 并明确各个角色的职责,让他们各司其职。
In most object-oriented languages, including Ruby, every object is an example or instance of a particular class, and the behavior of individual objects is determined at least to some extent by the method definitions present in the object’s class.
在面向对象语言中,所有的对象都是特定 class 的某个实例,这些 对象 的行为基本都是由他们所属的 class 中定义的 method 决定的。
—
实际上 class 也是一类object。
—
ruby 中每一个刚出生的 object ,都会带有一些先天的能力,也就是一些基本的可执行的 method。 更重要的是,这些object可以在后天,“学会”一些能力,这个过程就是 def 一个mothod 的过程。
“教会”一个object 某个 method:
The object obj understands, or responds to, the message talk. An object is said to respond to a message if the object has a method defined whose name corresponds to the message.
当一个object 能理解回应某个信息,那么可以说这个 object respond to 这个 message。
接上面的例子
关于参数
More precisely, the variables listed in the method definition are the method’s formal parameters, and the values you supply to the method when you call it are the corresponding arguments. (It’s common to use the word arguments, informally, to refer to a method’s parameters as well as a method call’s arguments, but it’s useful to know the technical distinction.)
parameter (技)参(变)数;参(变)量
argument 具体的参数值
定义 method 时括号中列出的内容是 parameters 呼叫 method 时传入的实际值被称为 arguments
ruby 中 除了 nil 和 false 以外的所有对象 使用 if 来判断,都会返回 True
注意 puts 最后的返回值是 nil
所以 !!one
最后返回的是 false ,这类逻辑判断要以最终返回值作为依据。
几个重要的 先天 methods
- object_id
- respond_to?()
- send(synonym: __send__)
12.5.0 :002 > obj = Object.new 2 => #<Object:0x00007fbabd83f138> 32.5.0 :003 > obj.object_id 4 => 70220010092700 52.5.0 :004 > obj.respond_to?(:respond_to?) 6 => true 72.5.0 :005 > obj.send(:object_id) 8 => 70220010092700 92.5.0 :006 > obj.send(:fake_meth) 10Traceback (most recent call last): 11 2: from /Users/caven/.rvm/rubies/ruby-2.5.0/bin/irb:11:in `<main>' 12 1: from (irb):6 13NoMethodError (undefined method `fake_meth' for #<Object:0x00007fbabd83f138>) 142.5.0 :007 > 15
BasiceObject 是 Object 的 superclass
它的实例对象能做的事很少,甚至不能使用 methods 方法查看实例能用的方法。
12.5.0 :001 > Object.superclass 2 => BasicObject 32.5.0 :002 > BasicObject.new 4(Object doesn't support #inspect) 5 => 62.5.0 :003 > BasicObject.new.methods 7Traceback (most recent call last): 8 2: from /Users/caven/.rvm/rubies/ruby-2.5.0/bin/irb:11:in `<main>' 9 1: from (irb):3 10NoMethodError (undefined method `methods' for #<BasicObject:0x00007fa43084ec80>) 112.5.0 :004 > 12
不过在 class 层面,两个class 虽然是父子关系,但是他们的 class methods 的内容和数量都是一样的。
send
用来向一个对象送出一个message 或者说让对象执行一个 method
但与直接使用 点号 . 呼叫方法不同
send 在这里可以接受一个参数,在不确定输入值是什么的时候,比如 消息是由客户端送出的,那么send 就会把这个动态的消息送给对象
这种不确定的情境 配合使用 respond_to?() 可以简化代码
比如我们写了一个 class Ticket, 用户可以查询自己想查的内容
1 def answer(request) 2 case request 3 when "date" 4 puts self.date 5 when "seat" 6 puts self.seat 7 when "price" 8 puts self.price 9 # imagine that every ticket has hundreds of attributes 10 else 11 puts "No such info" 12 end 13 end 14 15 def response(request) 16 if self.respond_to?(request) 17 puts self.send(request) 18 else 19 puts "No such info" 20 end 21 end
answer
方法只能针对固定的几个 attributes, 如果票的信息有很多种,那么代码会变得极其冗长。
而response
方法则直接根据用户的输入送 message 给 Ticket object 如果有对应的 attribute 就返回其值,没有就印出提示。其中就用到了 send
和 respond_to?
的配合。
定义 method 的时候给参数列表中的参数前加星号 可以传入任意数量的参数 并以 array 的形式组织。
如果带星号的参数在中间,有几种情况:
当给出的参数个数大于 parameters 个数时,带星号的参数会尽量多地吸收传入的参数到自己的 array 中
当给出的参数个数等于 parameters 个数时,带星号的参数会把对应位置的参数带入 array
当给出参数的个数 比指定 parameters 个数少一个时 带星号的参数会把位置让出来, 让自己变成一个 空 array
当给出参数个数比指定 parameters 少两个或以上时,报错
12.5.0 :001 > def sponge(a, b, *c, d) 22.5.0 :002?> p a, b, c, d 32.5.0 :003?> end 4 => :sponge 52.5.0 :004 > sponge(1,2,3,4,5,6,7,8) # 参数富余 61 72 8[3, 4, 5, 6, 7] 98 10 => [1, 2, [3, 4, 5, 6, 7], 8] 112.5.0 :005 > sponge(1,2,3,4) # 参数对等 121 132 14[3] 154 16 => [1, 2, [3], 4] 172.5.0 :006 > sponge(1,2,3) # 少一个参数 181 192 20[] 213 22 => [1, 2, [], 3] 232.5.0 :007 > sponge(1,2) # 少两个参数 24Traceback (most recent call last): 25 3: from /Users/caven/.rvm/rubies/ruby-2.5.0/bin/irb:11:in `<main>' 26 2: from (irb):7 27 1: from (irb):1:in `sponge' 28ArgumentError (wrong number of arguments (given 2, expected 3+)) 29
还有一种情况是 parameters 中既包括默认值设置,又包括 spongy 参数(带星号)
12.5.0 :001 > def args_unleashed(a, b=1, *c, d, e) 22.5.0 :002?> puts "Arguments:" 32.5.0 :003?> p a, b, c, d, e 42.5.0 :004?> end 5 => :args_unleashed 62.5.0 :005 > args_unleashed(1,2,3,4,5) 7Arguments: 81 92 10[3] 114 125 13 => [1, 2, [3], 4, 5] 142.5.0 :006 > args_unleashed(1,2,3,4) 15Arguments: 161 172 18[] 193 204 21 => [1, 2, [], 3, 4] 22 2.5.0 :009 > args_unleashed(1,2,3) 23Arguments: 241 251 26[] 272 283 29 => [1, 1, [], 2, 3] 302.5.0 :010 > 312.5.0 :007 > args_unleashed(1,2,3,4,5,6,7,8) 32Arguments: 331 342 35[3, 4, 5, 6] 367 378 38 => [1, 2, [3, 4, 5, 6], 7, 8] 392.5.0 :008 > args_unleashed(1,2) 40Traceback (most recent call last): 41 3: from /Users/caven/.rvm/rubies/ruby-2.5.0/bin/irb:11:in `<main>' 42 2: from (irb):8 43 1: from (irb):1:in `args_unleashed' 44ArgumentError (wrong number of arguments (given 2, expected 3+)) 45
可以说 这种情况下ruby 在做的是,合乎逻辑地,努力地,分配参数,尽量不让程序报错,直到它也无能为力为止
从上面只给 3个参数的情况可以看出这种 努力
因为 default value 和 sponge 参数都不是必须要传的,所以所有参数都分配给了 required 的参数。
几种参数的优先级排列是:
一个特例是不能以这种顺序排列 parameters
(x, *y, z=1)
it’s a syntax error, because there’s no way it could be correct. Once you’ve given x its argument and sponged up all the remaining arguments in the array y, nothing can ever be left for z. And if z gets the right-hand argument, leaving the rest for y, it makes no sense to describe z as “optional” or “default-valued.” The situation gets even thornier if you try to do something like the equally illegal (x, *y, z=1, a, b)
. Fortunately, Ruby doesn’t allow for more than one sponge argument in a parameter list. Make sure you order your arguments sensibly and, when possible, keep your argument lists reasonably simple!
定义方法的时候这样排列参数会是一个句法错误,因为这种顺序逻辑上不成立。当x分别到参数后,中间的 *y 会吸收掉所有剩下的参数。如果 z 直接拿最右边的参数,那么作为 optional 的特性也就不成立。 这种情况在(x, *y, z=1, a, b)
中也一样。ruby不允许参数列表中出现多于1个的sponge argument。确保你的参数列表合乎逻辑且尽量保持条理。(这段话存疑)
reference
(方便查询的)编号,标记,索引 A reference is something such as a number or a name that tells you where you can obtain the information you want.
在ruby 中
变数赋值的过程,并不是把等号右边的整个内容“装到” 左边的变数中
而是把右边内容整体给一个编号(可以理解为在内存中的位置标记) 让左边的变数记住
当把变数 继续赋值给 另一个变数时,也是给出的这个编号而不是实际内容
ruby 中的 replace 可以将已有编号位置的内容替换掉而不改变编号
而常用的 等号 赋值 = , 则是用新内容(可以与原内容一样)的编号换掉原来的编号
虽然string的内容相同,但他们是不同的对象。
12.5.0 :002 > str = "string" 2 => "string" 32.5.0 :003 > abc = str 4 => "string" 52.5.0 :004 > str.object_id 6 => 70131023811700 72.5.0 :005 > abc.object_id 8 => 70131023811700 92.5.0 :006 > str.replace("hello") 10 => "hello" 112.5.0 :007 > str.object_id 12 => 70131023811700 132.5.0 :008 > abc.object_id 14 => 70131023811700 152.5.0 :009 > str 16 => "hello" 172.5.0 :010 > str = "world" 18 => "world" 192.5.0 :011 > str.object_id 20 => 70131019765580 212.5.0 :012 > abc.object_id 22 => 70131023811700 232.5.0 :013 > str 24 => "world" 252.5.0 :014 > abc 26 => "hello"
在这几个原则下
使用 replace 可以让所有 “拿着” 这个编号的变数手里的内容同时改变
而使用 = 等号 则是将当前变数的编号和内容刷新,不会影响到之前拿着编号的变数
但就这个方面来说 replace 的操作更底层更有破坏力
但有一个例外是,对于数字类型的内容,同样的数值,内存中的编号也相同
所以无法使用 replace 来换掉对应编号的内容
从 rdoc 中也可以看到哪几种 类型 才有 replace method
这些原则不只适用于 local variable 也适用于 其他类型的变数 class, global
对于 array 来说情况也符合这个逻辑
整个 array 拿着一个 编号 array 中的 每个 element 实际也只是一个编号
所以只要不去动到 array 中的任何一个 编号,那么 array 还是被认为是原来的 array 即使部分编号的 内容被 replace 了
12.5.0 :001 > numbers = %w{one two three} 2 => ["one", "two", "three"] 32.5.0 :002 > numbers.freeze 4 => ["one", "two", "three"] 52.5.0 :003 > numbers[2].object_id 6 => 70126305599800 72.5.0 :004 > numbers[2].replace("hello") 8 => "hello" 92.5.0 :005 > numbers 10 => ["one", "two", "hello"] 112.5.0 :006 > numbers.object_id 12 => 70126305599780 132.5.0 :007 > numbers[2].object_id 14 => 70126305599800 152.5.0 :008 >
即使单个改变的 array 中某个元素 array 本身的 编号也不变
更符合视觉意象的是把这里的 array 两边的 [ ] 理解为两个编号相同的的 界桩
无论这两个界桩之内的内容是增加了,减少了,或是中间没有东西,不管这两个界桩之间的距离有多远,这两个界桩的编号是不变的
直到用两个有新编号的界桩(样子可以看起来一模一样)换掉他们
按照这个逻辑,只要我不换掉这两个 [ ],里面的东西随便怎么弄,array 的编号是不动的。
即使给元素中某个位置重新赋值,且这个位置的编号变化了,但却对整体 array 的编号没有影响
即使 array 中所有的元素都被改变 array 本身的 编号也不会改变
除非重新给 array 整体赋值 编号才改变
Summary
We’ve covered a lot of ground in chapter 2. In this chapter you saw How to create a new object and define methods for it 如何新建一个对象并给他定义方法
The basics of the message-sending mechanism by which you send requests to objects for information or action send 机制
Several of the important built-in methods that every Ruby object comes with: object_id, respond_to?, and send 3个内建的重要的关于ruby对象的基本方法,object_id, respond_to?, send
Details of the syntax for method argument lists, including the use of required, optional, and default-valued arguments 参数列表的细节,包括 required, default-valued, optional arguments
How local variables and variable assignment work 变数赋值是如何工作的
Ruby’s use of references to objects and how references play out when multiple variables refer to the same object 对象的引用以及多个变数引用同一个对象时这些引用如何工作。