赞 | 0 |
VIP | 0 |
好人卡 | 3 |
积分 | 1 |
经验 | 11523 |
最后登录 | 2021-6-12 |
在线时间 | 124 小时 |
Lv1.梦旅人
- 梦石
- 0
- 星屑
- 129
- 在线时间
- 124 小时
- 注册时间
- 2011-9-12
- 帖子
- 76
|
本帖最后由 mxymxy 于 2013-12-11 19:51 编辑
单纯多态(下)
昨天我讲到了单纯的多态,有过C++编程经验的人应该可以感受到Ruby语言本身的强大,根本不需要泛型,不需要虚方法,就实现了C++整了一堆概念都搞不好的问题。
那么,今天来讲一个最有争议的问题:多类继承。
不知大家注意到没,生活中的很多对象都不只有一个属性:椅子既可以坐(”Furniture”类的实例)又可以烧(”Wood”类的实例)。但是Ruby,还有其他大多数的语言,都只支持单类继承。也就是说Chair<Wood或者Chair<Furniture二选一,剩下的即使方法一样,也不能继承!纯生的面向对象——Smalltalk类大部分方法是共同的,所以几乎用不到多重继承。然而C++和Ruby呢?显然,他们需要(考虑istream类、ostream类和iostream类)。
Ruby不支持。也许你会疑惑为什么?不过我得承认在语言本身偏于杂乱(吸收多家语言杂合)的情况下,这样的选择是正确的。比如说,C同时继承A、B,而AB有一个重名的方法(例如initialize),那么运行的时候怎么算?覆盖,还是分立?如果像C++一样选择分立,那么,假设A、B又有一个共同父类D,那A、B中共有的D的部分怎么算?依然分立?那样不仅浪费空间,功能上也是不恰当的。比如说Wood和Furniture都是Good类的实例,Good类有一个Price(价格)的变量,那么当Chair调用Wood的父类的方法修改价格的时候,它从Furniture的父类那里得到的价格应该也应该改变。C++为了解决这个冲突又增加了虚继承等机制,不仅使语言内质变得繁琐杂乱,而且增加了额外的开销,还有可能造成其他冲突(最晚辈派生类的初始化问题等)。因此前辈们提倡在C++系列(不包括Ruby)中使用二次封装或直接继承公共基类而不是多重继承是有道理的。
然而,这里是重视语言强度胜过内在实现的Ruby,从理念上,它应该是尽一切可能使程序员把目光放在问题本身而不是实现上。从语言的哲学上,它支持纯粹的面向对象,没有数据类型差异和向上映射,它也不能支持多类继承不是很不好吗?于是它引入了一个新概念——MixIn(以下翻译作“混入继承”)。但是没有引入新的对象,而是把本来只是作为命名空间的模块直接拿来当做混入方法的容器了,这个是安全的。请看下例:
(附注:模块的类别其实是Class类的基类,所以二者表现相似,只是模块不允许实例化而已)
(附注2:昨晚楼上那位大神讲了Table.each真是太好了,因为我正需要这个!)
Ruby中有可比较对象(支持between?和各种比较运算符),有可枚举对象(支持all,any,collect,find等)。我们如果有一个表,它既可比较(字典序),又可枚举,怎么办呢?可以观察到可比较对象和核心是<=>方法(类似于C++中的纯虚方法),其他的如between?等在该方法实现之后其实现都大同小异;而可枚举对象的核心是each方法。于是我们这样:- module PEnumerable
- def all?(&b) # 不用做命名空间时,方法定义不要加self
- ret = true
- self.each {|v| ret = false unless b.(v) } # 这里使用到了未定义的核心方法each
- return ret
- end
- end
- module PComparable
- def ==(b)
- return self.<=>(b) == 0
- end
- def >(b)
- return self.<=>(b) > 0
- end
- def <(b)
- return self.<=>(b) < 0
- end
- end
- class Table
- include PEnumerable,PComparable # 这里混入继承了多个模块,作为多类继承的替代。
- # 为了演示这一实现,楼上不要include的话我就果断无视了啊!
- def each
- 0.upto(xsize-1) do |x|
- 0.upto(ysize-1) do |y|
- 0.upto(zsize-1) do |z|
- yield self[x,y,z]
- end
- end
- end
- self
- end
- def <=>(b)
- 0.upto([xsize,b.xsize].min-1) do |x|
- 0.upto([ysize,b.ysize].min-1) do |y|
- 0.upto([zsize,b.zsize].min-1) do |z|
- return 1 if self[x,y,z]>b[x,y,z]
- return -1 if self[x,y,z]<b[x,y,z]
- end
- end
- end
- return 0
- end
- end
复制代码 然后试一下:- tb = Table.new(2,2,2)
- tb[0,0,0]=0
- tb[0,0,1]=1
- tb[0,1,0]=2
- tb[0,1,1]=3
- tb[1,0,0]=4
- tb[1,0,1]=5
- tb[1,1,0]=6
- tb[1,1,1]=7
- tb.each {|v| p v} # 输出0-7
- p tb.all? {|v| v > 0} # =>false
- p tb.all? {|v| v < 8} # =>true
复制代码 再试下比较:- t2 = Table.new(1,1,1)
- t2[0,0,0]=1
- p tb < t2 # => true
复制代码 实际上Ruby1.9实现了很完备的Comparable模块和Enumerable模块,我们可以直接用。而RGSS3的其他模块大多是用来当命名空间的,即使混入继承了也没加入啥新方法,所以就不用。而Math这个模块特殊,它的每一个方法都同时定义了self.sin(x)和sin(x)两个实现相同的版本,所以既可以混入继承又可以当命名空间调用。只是注意当你混入继承某个模块的时候一定先把这个模块的核心方法实现了,否则会爆出“未定义的方法”错误。
最后,我们如何给模块的方法改名?直接alias是不行的。国外的某大神教我们这样做:- module DataManager
-
- # 设置别名,很奇葩的方法→_→
- class <<self
- alias :load_database_HZ2 :load_database
- alias :setup_new_game_HZ2 :setup_new_game
- end
-
- #--------------------------------------------------------------------------
- # ● 读取数据库
- #--------------------------------------------------------------------------
- def self.load_database
- load_database_HZ2
- # 做某事
- end
-
- #--------------------------------------------------------------------------
- # ● 设置新游戏
- #--------------------------------------------------------------------------
- def self.setup_new_game
- setup_new_game_HZ2
- # 做某事
- end
-
- end
复制代码
不知道多久之后的下一章我打算讲lambda演算。老实说,整个66RPG上讲块、方法、lambda表达式三者讲得清楚的帖子我就见过一个,还只是说了下区别,没有讲其中的逻辑……
|
|