Project1

标题: coerce方法,是义在哪个类? [打印本页]

作者: Infrared    时间: 2008-6-1 00:18
提示: 作者被禁止或删除 内容自动屏蔽
作者: link006007    时间: 2008-6-1 00:36
Numeric 类
另外 一个ruby标准库里的复数类 .
还是不贴了 lz自己写的挺好
要的话可以自己去找ruby的标准库
作者: hitlerson    时间: 2008-6-1 00:39
  1. 尽管技术上不属于Ruby语言的一部分,但解释器和标准库会使用各种各样的协议来处理其他语言在使用类型时遇到的一些问题。

  2. 一些对象会有多种自然表示。比如,你可能会编写一个类来表示罗马数字(I, II, III, IV, V等等)。这个类并不一定是Integer的子类,因为其对象是数字的表示,自己未必就是数字。同时它们真的有点类似整数(interger)的特点。无论何时当Ruby预期见到整数时,如果能够使用我们罗马数字类的对象,就太好了。

  3. 为此,Ruby提供了转换协议(conversion protocols)的概念——对象可以选择把自己转换成另外一个类的对象。Rudy有三种标准的方式来实现它。

  4. 我们已经遇到了第一种方式。诸如to_s和to_i方法分别把它们的接收者转换成字符串和整数。这些转换方法不是非常严格的:比如,如果对象有某种得体的字符串表示,它可能会有to_s方法。为了返回数字的字符串表示(比如VII),我们的Roman类可能会实现to_s方法。

  5. 第二种形式的转换函数使用名字如to_str和to_int的方法。它们是严格的转换函数:只有当对象能够很自然地用在字符串或者整数能够使用的任何地方,才应该实现它。比如,罗马数字的对象可以清楚地表示一个整数,因此应该实现to_int方法。但是涉及字符串的严格转换时,我们得好好想一想。

  6. 罗马数字显然可以用字符串来表示,但是这些数字是字符串吗?我们能在任何使用字符串的地方使用这些数字吗?不,可能不。从逻辑上讲,它们是数字的表示。你可以把它们表示成字符串,但是它们和字符串并非是插入兼容(plug-compatible)。因为这个原因,罗马数字不应实现to_str——它不是真正的字符串。总结一下:使用to_s,罗马数字可以转换成字符串,但是它本质上不是字符串,因此没有实现to_str。

  7. 为了看看这实际上是如何工作的,我们考察一下打开的文件。 File.new的第一个参数可能是已有的文件描述符(用数字表示)或是要打开的文件名。当然,Ruby不是只看第一个参数还检查类型是否是Fixnum 或String。相反,它给被传递进来的对象一个机会,让这个对象以数字或字符串来表示自己。如果用Ruby编写,它会写成这样:

  8. class File

  9.   def File.new(file, *args)

  10.     if file.respond_to?(:to_int)

  11.       IO.new(file.to_int, *args)

  12.     else

  13.       name = file.to_str

  14.       # call operating system to open file 'name'

  15.     end

  16.   end

  17. end

  18. 如果想传递保存在罗马数字中的文件描述符整数到File.new中,我们看看到底发生了什么。因为我们的类实现了to_int,第一个 respond_to?测试会成功、我们把数字的整数表示传递到IO.open,同时返回文件描述符,所有这一切都被包装到新的IO对象中。

  19. 少数几个严格的转换函数被内建到标准库中。

  20. to_ary→Array

  21. 用在当解释器需要把对象转换成数组以传递参数或进行多个赋值时。

  22. class OneTwo

  23.   def to_ary

  24.     [ 1, 2 ]

  25.   end

  26. end

  27. ot = OneTwo.new

  28. a, b = ot

  29. puts "a = #{a}, b = #{b}"

  30. printf("%d-­%d\n", *ot)

  31. 输出结果:

  32. a = 1, b = 2

  33. 1 ­- 2

  34. to_hash→ Hash

  35. 用在当解释器期待看到散列表时(唯一已知的用法是Hash#replace的第二个参数)。

  36. to_int → Integer

  37. 用在当解释器期待看到整数值时(如文件描述符或Kernel.Integer的参数)。

  38. to_io → IO

  39. 用在当解释器期待IO对象时(比如,IO#reopen或IO.select的参数)。

  40. to_proc → Proc

  41. 用在转换方法调用中的前缀有&符号的对象。

  42. class OneTwo

  43.   def to_proc

  44.     proc { "one-two"}

  45.   end

  46. end

  47. def silly

  48.   yield

  49. end

  50. ot = OneTwo.new

  51. silly(&ot) → "one-two"

  52. to_str → String

  53. 普遍用在任何解释器寻找String值的地方。

  54. class OneTwo

  55.   def to_str

  56.     "one-two"

  57.   end

  58. end

  59. ot = OneTwo.new

  60. puts("count: " + ot)

  61. File.open(ot) rescue puts $!.message

  62. 输出结果:

  63. count: one-two

  64. No such file or directory ­one-two

  65. 请注意,当然to_str使用的不是很普遍——一些想要字符串参数的方法不会调用to_str。

  66. File.join("/user", ot) → "/user/#<OneTwo:0x1c974c>"

  67. to_sym→Symbol

  68. 表示接收者是符号。解释器没有使用它进行转换并且可能在用户代码中是无用的。

  69. 最后要说明的是:诸如Integer和Fixnum的类实现了to_int方法,String类实现了to_str方法。这样可以多态地调用这些严格的转换函数:

  70. # it doesn't matter if obj is a Fixnum or a

  71. # Roman number, the conversion still succeeds

  72. num = obj.to_int

  73. 23.3.1  数字强制转换

  74. Numeric Coercion

  75. 在第372页讲过解释器可以执行三种类型的转换。已经讨论了宽松的和严格的转换。第三种是数字强制转换(numeric coercion)。

  76. 问题是这样的。当写“1+2”时,Ruby知道在对象1(Fixnum)上调用+,把Fixnum 2作为参数传递给它。但是当写“1+2.3”时,同样的+方法现在接受Float参数。它如何知道该做些什么呢(尤其详细检查参数的类型违背了duck typing的精神)?

  77. 答案在Ruby中的基于coerce方法的强制(coercion)协议。coerce的基本操作很简单。它接受两个数字(一个是接受者,另外一个是参数)。它返回包含两个元素的数组,分别是两个数字的表示(但是参数在前,后面跟着接受者)。coerce方法保证两个

  78. 对象会有相同的类,因此可以对它们进行相加(或乘,或比较,或任何别的什么操作)。

  79. 1.coerce(2)           → [2, 1]

  80. 1.coerce(2.3)         → [2.3, 1.0]

  81. (4.5).coerce(2.3)            → [2.3, 4.5]

  82. (4.5).coerce(2)       → [2.0, 4.5]

  83. 技巧在于接受者调用其参数的coerce方法来生成数组。这种技术被称为两次分发(double dispatch),它允许方法根据它的类并且还有参数的类来改变自身的行为。在这个例子中,我们让参数决定到底应该对哪个类的对象进行相加(或乘,除等等)。

  84. 假设正在编写新的类来进行算术计算。为了使用强制转换,需要实现coerce方法。它接受一些其他类型的数字作为参数,并返回一个数组,其中包含同一类的两个对象,它们的值与其参数及其自身相等。

  85. 对于罗马数字类来说这相当简单。每个罗马数字对象在内部把它的实际值以Fixnum方式保存在实例变量@value中。coerce方法检查看参数的类是否也是Integer。如果是,则返回参数和这个内部值。如果不是,则优先把两者都转换成浮点数。

  86. class Roman

  87.   def initialize(value)

  88.     @value = value

  89.   end

  90.   def coerce(other)

  91.     if Integer === other

  92.       [ other, @value ]

  93.     else

  94.       [ Float(other), Float(@value) ]

  95.     end

  96.   end

  97.   # .. other Roman stuff

  98. end

  99. iv = Roman.new(4)

  100. xi = Roman.new(11)

  101. 3 * iv    → 12

  102. 1.1 * xi → 12.1

  103. 当然,以此种方式实现的Roman类还不知道如何相加:无法在先前例子中编写“xi + 3”,因为Roman类没有“plus”方法。也许它就应当这样。但是不管这些,让我们为罗马数字实现相加方法吧。

  104. class Roman

  105.   MAX_ROMAN = 4999

  106.   attr_reader :value

  107.   protected :value

  108.   def initialize(value)

  109.     if value <= 0 || value > MAX_ROMAN

  110.       fail "Roman values must be > 0 and <= #{MAX_ROMAN}"

  111.     end

  112.     @value = value

  113.   end

  114.   def coerce(other)

  115.     if Integer === other

  116.       [ other, @value ]

  117.     else

  118.       [ Float(other), Float(@value) ]

  119.     end

  120.   end

  121.   def +(other)

  122.     if Roman === other

  123.       other = other.value

  124.     end

  125.     if Fixnum === other && (other + @value) < MAX_ROMAN

  126.       Roman.new(@value + other)

  127.     else

  128.       x, y = other.coerce(@value)

  129.       x + y

  130.     end

  131.   end

  132.   FACTORS =             [["m", 1000],          ["cm", 900],    ["d", 500],  ["cd", 400],

  133.            ["c",        100],            ["xc",     90], ["l",     50],  ["xl",    40],

  134.            ["x",          10],            ["ix",       9], ["v",       5],  ["iv",    4],

  135.            ["i",          1]]

  136.   def to_s

  137.     value = @value

  138.     roman = ""

  139.     for code, factor in FACTORS

  140.       count, value = value.divmod(factor)

  141.       roman << (code * count)

  142.     end

  143.     roman

  144.   end

  145. end

  146. iv = Roman.new(4)

  147. xi = Roman.new(11)

  148. iv + 3                →            vii

  149. iv + 3 + 4           →            xi

  150. iv + 3.14159    →            7.14159

  151. xi + 4900            →            mmmmcmxi

  152. xi + 4990            →            5001

  153. 最后,应当小心使用coerce——尝试总是强制转换到更通用的类型,否则可能最终造成强制转换循环,在那里A试图强制转换到B,而B试图强制转换回到A。
复制代码

作者: Infrared    时间: 2008-6-1 01:32
提示: 作者被禁止或删除 内容自动屏蔽
作者: IamI    时间: 2008-6-1 01:50
这儿有个问题。当你写”1+2”时,Ruby知道在对象1(a Fixnum)上调用+号,将Fixnum2做为参数传递给它。但是,当你写”1+2.3”时,同样的+方法立刻接受一个Float参数。它是如何知道做什么的呢(用”接口类型”精神来直接地检查你的参数的类)?

答案是Ruby基于方法coerce的强制协议。coerce的基本操作很简单。它接受两个数字(一个做为它的被调,另一个做为参数)。它返回包含表示这两个数字的一个两元素的数组(但是跟在第一个参数后面的才是被调)。coerce方法保证这两个对象有同样的类,所以它们可以被添加(或乘,或比较,或其它什么)。

1.coerce(2) => [2, 1]

1.coerce(2.3) => [2.3, 1.0]

(4.5).coerce(2.3) => [2.3, 4.5]

(4.5).coerce(2) =>[2.0, 4.5]

秘密是被调调用它的参数的coerce方法来产生这个数组。这个技术,被称为双重分派(double dispatch),它允许一个方法不仅可在它自己的类上修改它的行为,也可以在它的参数的类上修改它的行为。这种情况下,我们使用参数来正确地决定,应该被加(或乘,除等等)的对象的类是什么。

让我们写个新类,它的目的获得算法部分。要想用上强制,我们需要实现一个coerce方法。它接受一些其它种类数字

那么在Complex类当中重新定义这个方法并且用is_a来判断其类型,但是要确定的是最后输出的是什么类型,并且应该同时对传来的参数和self执行转换 [LINE]1,#dddddd[/LINE]系统信息:本贴由楼主认可为正确答案,66RPG感谢您的热情解答~




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1