设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 11390|回复: 42
打印 上一主题 下一主题

[原创发布] 【新手互助】利用Mix-in(糅合)构建低耦合脚本

[复制链接]

Lv1.梦旅人

梦石
0
星屑
55
在线时间
14 小时
注册时间
2008-5-7
帖子
74
跳转到指定楼层
1
发表于 2010-8-19 12:51:09 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
写在最前:以下观点来源于新手对RGSS脚本系统的一些浅显认知,希望能够给其他新手一些启发;牛人请直接跳过。如感觉不妥,请善意的指出;对于自认为很牛的无故拍砖者,一律鄙视!
原文链接:http://rpg.blue/home.php?mod=spa ... id=47826&id=689

include(module ... ) 对指定模块的性质(方法或常数)进行添加。返回 self。include 正是为实现 Mix-in(糅合)功能而设计的,而 Mix-in 取代了多重继承。
以上为RM中针对于关键字include的官方解释,本篇后续的内容亦是围绕此关键字展开。
Train_Actor是我接触RGSS以来遇到的第一个脚本系统,原帖可参见本坛脚本库,我空间里也有。用过的人应该都知道,只要借助于它便可轻易的实现角色跟随的特效。而引入此脚本的方法也很简单,只要在Main之前插入一段新脚本页,全部内容Copy即可。可谓便捷、经典。

第一次用这个脚本的时候觉得很神奇:该脚本完全独立,而系统是如何在恰当时机自动调用这个新增脚本内部方法的呢?经过一些简单的验证之后,终于渐渐发现了问题所在。

不知大家有没有留意到,该脚本的末端,也就是在module Train_Actor声明完毕之后,还有如下代码:

class Game_Party
include Train_Actor::Game_Party_Module
end
class Game_Player
include Train_Actor::Game_Player_Module
end
class Spriteset_Map
include Train_Actor::Spriteset_Map_Module
end
class Scene_Map
include Train_Actor::Scene_Map_Module
end

结合官方对于include的解释,不难猜想,这种独立脚本的自动调用机制即是由此而来。

所谓“对指定模块的性质(方法或常数)进行添加”,简单而言,也就是让自己脚本中已定义模块的方法或常数,成为指定模块中相应方法的逻辑补充。即是说,系统在调用原有模块内部方法的同时,会自动对已经被include至该模块的其他模块内部方法或常数进行调用。

有点绕口,我们不妨作如下实验来说明问题:

Main之前插入一个新脚本页,名字随便,而后写入如下代码:

module TestInclude
    def update
    p "外部方法被调用"   #验证一下自己的方法是否被调用
    super
    end
end
class Game_Player
    include TestInclude
end

代码很简单也很好理解,定义一个名为TestInclude的模块,此模块内部含有一个update方法,之后我们将此模块include至Game_Player模块,这样一来,系统对Game_Player的update方法调用完毕之后将自动调用TestInclude里的update——也就是说,我们自己模块中的方法成为了系统自带的Game_Player模块中update方法的逻辑补充。

点F12执行,则带有“外部方法被调用”字样的对话框会弹出。由于系统对于Game_Player里的update是一直调用的,因此我们自己模块里的update也会一直向外弹对话框,从任务管理器里将Game.exe扼杀掉即可。

之前也看过神思大人写的战斗CP脚本系统,有很多新人反应不知道该怎样引入自己的工程。我想如果借助于这种方法,神思大人完全可以将自己的CP系统也改装成不依赖于原有系统脚本且随用随引的独立系统了。

至于官方注释中提到的多重继承,这是C++中一种稍复杂的机制,这里不再讨论。其实在本人看来,Mix-in更像是多重反向继承。大家细心的话,应该就可以发现,对于include的外部模块,系统是优先调用原模块的方法,而后调用include模块中的方法,与C++中继承机制的调用顺序相反。

评分

参与人数 1星屑 +240 收起 理由
六祈 + 240 鼓励一下原创~~希望有更多人加入进来讨论~~ ...

查看全部评分

Lv2.观梦者

旅之愚者

梦石
0
星屑
275
在线时间
812 小时
注册时间
2007-7-28
帖子
2148

贵宾

2
发表于 2010-8-19 12:55:25 | 只看该作者
对于include的外部模块,系统是优先调用原模块的方法,而后调用include模块中的方法,与C++中继承机制的调用顺序相反。
独孤残云 发表于 2010-8-19 12:51

如果按照你的说法,那么调用update时就该调用原类里的方法,那么外部模块的update并不会被调用
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
55
在线时间
14 小时
注册时间
2008-5-7
帖子
74
3
 楼主| 发表于 2010-8-19 13:11:01 | 只看该作者
回复 六祈 的帖子
C++中的继承机制通常体现为:子类重写方法--->基类方法
Ruby中的include体现为:原有方法--->补充方法
如果
class A
include module B
End
按文中的说法,我是将B视为A的子类,其实这种说法可能并不是很严密。
六祈大人如果觉得有什么不妥,不妨将Game_Player.update()方法也加一句p输出,一试便知。

   

点评

如果将ruby的mix-in看做多重继承的话,那么include的module应该是父类。另外update是实例方法,不能由类来调用  发表于 2010-8-19 13:28
回复 支持 反对

使用道具 举报

Lv2.观梦者

旅之愚者

梦石
0
星屑
275
在线时间
812 小时
注册时间
2007-7-28
帖子
2148

贵宾

4
发表于 2010-8-19 13:26:15 | 只看该作者
回复 独孤残云 的帖子
呵呵,你学过C++,其实结合ruby能产生很多新的思路

但是呢,ruby的mix-in可能和你臆测的有所出入

mix-in其实不太像继承,由module带入的方法并不是super或者sub,而是以定义的先后来确定的

比如
module A
def a
p "i\'m module method"
end
end

class B
include A
def a
p "i\'m B\'s method"
end
end

B.new.a                       #i'm B's method

class C
def a
p "i\'m C\'s method"
end
include A
end

C.new.a                   #i'm module method




以上
所以如果是在类定义完之后
class SomeClass
include SomeModule
end

模块的同名方法就会覆盖类的方法
   
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
55
在线时间
14 小时
注册时间
2008-5-7
帖子
74
5
 楼主| 发表于 2010-8-19 13:33:32 | 只看该作者
回复 六祈 的帖子
六祈大人说的很对,我同意你的观点。
文中的最后一段意在补充说明官方解释的后半句,至于我本人,本来就没打算将二者并立而论。
第三帖只是对六祈大人以最后一句论事发第二帖的回复。还望莫要误解~

   

点评

咱们纯属讨论,不用拘谨~  发表于 2010-8-19 13:41
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
55
在线时间
14 小时
注册时间
2008-5-7
帖子
74
6
 楼主| 发表于 2010-8-19 13:35:21 | 只看该作者
回复 六祈 的帖子
另,我C++并非只是“学过”,如果六祈大人有时间,我们可以好好切磋一番~

   

点评

表示愚者C++是一点都不会的说~~~  发表于 2010-8-19 13:42
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
7
发表于 2010-8-19 13:53:55 | 只看该作者
这个机制确实可以用来实现多继承的工程,和 Java、C# 的接口是相同的概念
在 Ruby 中,一个模块 A 包含另一个模块 B 后,模块 B 中的常量、类变量和实例方法会被绑扎到一个匿名模块中,并隐式地让模块 A 继承自这个匿名模块,所以如果在 Mix-in 的时候模块 A 中已经有了模块 B 中的实例方法,就会出项方法的覆盖,而在该方法中通过 super 就能调用父模块中的同名方法;然而如果子模块 B 中的该实例方法并没有调用 super,这个实例方法就不会调用匿名父模块的同名方法
楼主给的这个例子里之所以 TestInclude 的 update 被调用,是因为 Game_Player#update 里调用了 super。可以在 Game_Player#update 里的 super 前后分别插入 p 1、p 2,就不难验证上述理论。这种方法在 RGSS 默认类中的默认实例方法有 super 的情况下十分好用,但如果没有 super,就无法实现所谓的“糅合”了
大家细心的话,应该就可以发现,对于include的外部模块,系统是优先调用原模块的方法,而后调用include模块中的方法,与C++中继承机制的调用顺序相反。

这个取决于 super 在原方法主体前还是后,甚至可能在中间,所以谈不上绝对的先后顺序
回复 支持 反对

使用道具 举报

Lv3.寻梦者

宛若

梦石
0
星屑
1568
在线时间
526 小时
注册时间
2007-8-19
帖子
1493

极短24参与开拓者

8
发表于 2010-8-19 14:12:55 | 只看该作者
本帖最后由 逸豫 于 2010-8-19 14:15 编辑

无视我……我是来乱入的……
恩……那啥不是叫做混成么……
那个……之所以方法被重定义是因为include是在方法定义后才对……

其实汝这样做貌似Game_Player的Update不会执行……执行的是Game_Player的父类Game_Character的update方法……对于方法的增添我们更常使用的是alias

有关alias更详细的说明请PIA这里
[url=http://rpg.blue/thread-219730-1-1.html]http://unhero.sinaapp.com/wi.php[/url]
[color=Red]如你所见这是个死坑,没错这就是打我的脸用的[/color]
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
9
发表于 2010-8-19 14:44:38 | 只看该作者
回复 逸豫 的帖子

include 在前还是在后无所谓,被 include 的模块中的东西是被捆绑到匿名父模块中的,而并没有替换当前模块中的同名方法:
  1. module A
  2.   def foo
  3.     p 1
  4.   end
  5. end

  6. class C
  7.   def foo
  8.     p 2
  9.   end
  10.   include A
  11. end

  12. c = C.new
  13. c.foo          # 2
复制代码
Game_Player#update 是执行了的,其内部通过 super 调用了 TestInclude#update,而  TestInclude#update 又通过 super 调用了 Game_Character#update

点评

哦……成步堂…… 链表的比喻很形象呢~ 果然没有测试就在那里随便下定义什么的是不对的呢……  发表于 2010-8-19 15:11
诚如紫苏大人所言,受教了~~~  发表于 2010-8-19 14:51
多次 include 的过程有点像链表的插入,在一个继承链中我们需要建立一个新的匿名模块节点,让子模块继承自它,又要让它继承自原来的子模块的父模块   发表于 2010-8-19 14:46
回复 支持 反对

使用道具 举报

Lv1.梦旅人

前进之卒

梦石
0
星屑
55
在线时间
20 小时
注册时间
2010-8-17
帖子
176
10
发表于 2010-8-19 14:54:48 | 只看该作者
现在还比较少接触include,看完这个似乎有点懂了,几位大人能否详细说明下其与super间的区别么?
开始拼吧!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-11-17 00:13

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表