Project1
标题: 【用代码说话】#2.史莱姆巢穴 [打印本页]
作者: 喵呜喵5 时间: 2016-7-31 00:52
标题: 【用代码说话】#2.史莱姆巢穴
本帖最后由 喵呜喵5 于 2016-7-31 01:00 编辑
【前言】
前篇传送门:https://rpg.blue/forum.php?mod=viewthread&tid=394674
在上一篇教程中,我曾举了一个史莱姆的例子来说明为什么要使用面向对象。不过,略微有些遗憾的是,上一篇的知识只是面向对象思想的冰山一角,所以,这一篇可以看作是对上一篇内容的一个延续和补充。通过这一篇的内容,我希望能够让你真正开始了解面向对象为了什么、做了什么、抽象了什么。当然,我承认,即使搞不懂面向对象也不代表你就无法写出一个完整的脚本了,但是,如果你我都能从这个简短而又啰嗦的小教程中学到什么的话,我将倍感欣慰。
出于让代码易于阅读的目的,在这次的示例中,各种命名我使用的是英文(而非拼音或汉字),不过,请不用担心,相应单词的中文释义我都将通过注释的方式和代码一并给出
那么,没有意义的废话就此打住,下面,让我们一起进入史莱姆的巢穴——面向对象的世界吧
【用代码说话】#2.史莱姆巢穴
引
- class Slime
- def appear; @hp = 10; end
- def die; @hp = 0; end
- def bark
- if @hp == 0
- p 'Uuuu...'
- else
- p 'GaO!!'
- end
- end
- end
- enemy1 = Slime.new
- enemy2 = Slime.new
- enemy1.appear
- enemy2.appear
- enemy1.bark
- enemy2.bark
- enemy1.die
- enemy1.bark
- enemy2.bark
- enemy3 = Slime.new
- enemy3.appear
- enemy3.bark
- # Slime - 史莱姆
- # appear - 出现
- # die - 死亡
- # bark - 吠叫
- # enemy - 敌人
复制代码
请不要借助任何代码执行工具,阅读上面的代码并思考,输出的结果是什么?
解
这次的教程我决定从一个传统的日式 RPG 故事作为开始:
一个新人冒险者接到了他冒险生涯中的第一个任务——讨伐三只史莱姆,从公会的接待那里领到了史莱姆的图鉴后,他兴致勃勃的踏上了旅途,没走多远,两个敌人嘎哦嘎哦的叫着从草丛中出现了……
“嘿,这就是史莱姆,他们的行为举止果然和图鉴上写的一模一样!”
一边注意着周围的环境,冒险者打倒了了其中一只史莱姆
“真是绝情啊,明明自己的同伴已经死亡了,另外这只史莱姆却仿佛什么都没发生一样的”
冒险者一边看着发出呜呜的声音瘫软在路边的史莱姆,一边开始将集中精力应对起剩下的那只史莱姆
这时候,从冒险者的身后,第三只史莱姆精力充沛的叫着嘎哦出现了!
从1967年第一种面向对象的语言 Simula67 发明到你阅读这篇教程的这个时间节点,编程语言已经发生了太多的改变,不论是内存控制亦或是垃圾收集,多亏了编程语言和硬件性能的发展,我们得以从各种乏味单调的任务中解脱从而将精力放到运行逻辑本身。鉴于继续顺着这个话题说下去估计这篇教程的主题就回不来了,我们就此打住,用代码说话
回到我们最开始的故事,冒险者公会将史莱姆写成图鉴,史莱姆的行动和图鉴一模一样,史莱姆1死了以后,史莱姆2仿佛什么都没变,史莱姆3在史莱姆1死了以后仍然精力充沛的出现,这就是面向对象的特征
下面,我们可以从头开始完整的分析一遍代码了,我们一起从上往下看一下
- class Slime
- def appear; @hp = 10; end
- def die; @hp = 0; end
- def bark
- if @hp == 0
- p 'Uuuu...'
- else
- p 'GaO!!'
- end
- end
- end
复制代码
- 冒险者公会写了一本怪物图鉴,定义了一个叫 Slime 的类
- Slime 有一个名叫出现的行为,当他执行这个行为的时候,他的 @hp 会变为 10
- Slime 有一个名叫死亡的行为,当他执行这个行为的时候,他的 @hp 会变为 0
- Slime 有一个名叫吠叫的行为,当他的 @hp 为 0 时,他的叫声是呜呜呜,当他的 @hp 为 10 时,他的叫声是嘎哦
- enemy1 = Slime.new
- enemy2 = Slime.new
- enemy1.appear
- enemy2.appear
- enemy1.bark
- enemy2.bark
- enemy1.die
- enemy1.bark
- enemy2.bark
- enemy3 = Slime.new
- enemy3.appear
- enemy3.bark
复制代码
- 敌人1是一只史莱姆
- 敌人2也是一只史莱姆
- 敌人1出现了!
- 敌人2出现了!
- 敌人1开始吠叫
- 敌人2开始吠叫
- 敌人1死了
- 敌人1开始吠叫
- 敌人2开始吠叫
- 敌人3是一只史莱姆
- 敌人3出现了!
- 敌人3开始吠叫
这段代码运行后的结果是
- "GaO!!"
- "GaO!!"
- "Uuuu..."
- "GaO!!"
- "GaO!!"
复制代码
在上一篇中,我们曾经说到,对于面向编程来说,每一个实例都是不同的,就好比这里的敌人1、敌人2、敌人3是三只不同的史莱姆一样,那么,这三只史莱姆明明行为都是一样的,为什么却需要让他们不同呢,这里就引出了各个实例“状态”的作用,在这次的例子里,就是三只史莱姆的HP,也就是代码中出现的 @hp 的功能。正因为像这种以 @ 开头的变量是用来表明实例状态的,所以我们称呼他为“实例变量”
实例的变量的存在让一个个实例的状态不再相同,在 class Slime 中,我们定义了史莱姆的行为,在实例 enemy1、enemy2、enemy3 中,我们储存了史莱姆的状态,从冒险者的角度来看,史莱姆在做的事情仅仅只是吠叫而已,冒险者无法关注到史莱姆吠叫时的 if else 逻辑,冒险者也没有必要去关注史莱姆吠叫时的 if else 逻辑。将不确定因素进行封装来让代码易于理解,这正是面向对象的编程所期望的
当然,虽然吹了半天面向对象,但是没有面向对象并不代表你就写不出代码了,例如,上面那段代码完全可以用下面这段面向过程的代码来等效替代
- def appear; return 10; end
- def die; return 0; end
- def bark(hp)
- if hp == 0
- p 'Uuuu...'
- else
- p 'GaO!!'
- end
- end
- enemy1 = 'Slime'
- enemy2 = 'Slime'
- enemy1_hp = appear
- enemy2_hp = appear
- bark(enemy1_hp)
- bark(enemy2_hp)
- enemy1_hp = die
- bark(enemy1_hp)
- bark(enemy2_hp)
- enemy3 = 'Slime'
- enemy3_hp = appear
- bark(enemy3_hp)
复制代码 如果场上同时出现三只史莱姆时,两种编程方式只从行数上来看似乎相差也不大……
- enemy1 = Slime.new
- enemy1.appear
- enemy2 = Slime.new
- enemy2.appear
- enemy3 = Slime.new
- enemy3.appear
复制代码- enemy1 = 'Slime'
- enemy1_hp = appear
- enemy2 = 'Slime'
- enemy2_hp = appear
- enemy3 = 'Slime'
- enemy3_hp = appear
复制代码 但是,当每只史莱姆除了拥有HP这个状态,还拥有 MP、TP、EXP、LV、ATK、DEF 这些状态的时候……
(@atk 论坛 @ bug修正)
- class Slime
- def appear
- @hp = 10
- @mp = 10
- @tp = rand(5)
- @exp = rand(5)
- @lv = rand(5)
- @atk = rand(5)
- @def = rand(5)
- end
- def die; @hp = 0; end
- def bark
- if @hp == 0
- p 'Uuuu...'
- else
- p 'GaO!!'
- end
- end
- end
- enemy1 = Slime.new
- enemy1.appear
- enemy2 = Slime.new
- enemy2.appear
- enemy3 = Slime.new
- enemy3.appear
复制代码- def appear_hp; return 10; end
- def appear_mp; return 10; end
- def appear_tp; return rand(5); end
- def appear_exp; return rand(5); end
- def appear_lv; return rand(5); end
- def appear_atk; return rand(5); end
- def appear_def; return rand(5); end
- def die; return 0; end
- def bark(hp)
- if hp == 0
- p 'Uuuu...'
- else
- p 'GaO!!'
- end
- end
- enemy1 = 'Slime'
- enemy1_hp = appear_hp
- enemy1_mp = appear_mp
- enemy1_tp = appear_tp
- enempy1_exp = appear_exp
- ………………$%$^%$@#$@
复制代码 当然,你可能会利用 hash 把你的代码写成这样
- def appear
- return {
- :hp => 10,
- :mp => 10,
- :tp => rand(5),
- :exp => rand(5),
- :lv => rand(5),
- :atk => rand(5),
- :def => rand(5),
- }
- end
- def die; return 0; end
- def bark(hp)
- if hp == 0
- p 'Uuuu...'
- else
- p 'GaO!!'
- end
- end
- enemy1 = 'Slime'
- enemy1_state = appear
- enemy2 = 'Slime'
- enemy2_state = appear
- enemy2 = 'Slime'
- enemy2_state = appear
复制代码 那么,恭喜你,你已经在很大程度上理解了面向对象编程所做的事情是什么了哦,在你的代码里 enemy1_state[:hp] 对应的就是面向编程中 enemy1 实例的实例变量 @hp 所承担的作用
如果你对 Proc 略有了解,更进一步,把你的代码改写成这样
- def appear
- die = Proc.new{ 0 }
- bark = Proc.new{|hp|
- if hp == 0
- p 'Uuuu...'
- else
- p 'GaO!!'
- end
- }
- return {
- :hp => 10,
- :mp => 10,
- :tp => rand(5),
- :exp => rand(5),
- :lv => rand(5),
- :atk => rand(5),
- :def => rand(5),
- :die => die,
- :bark => bark
- }
- end
- enemy1 = 'Slime'
- enemy1_state = appear
- enemy2 = 'Slime'
- enemy2_state = appear
- enemy2 = 'Slime'
- enemy2_state = appear
复制代码 那么,你的代码已经越来越接近面向对象的思维了呢。实际上,对于类似 lua 或者 javascript 这样的语言,这就是他们面向对象的实现方式。当然,这里要特别说明的是,虽然现阶段面向对象在你眼中可能只是一个语法糖一样的东西,实际上,他却和面向过程存在编程思想上的区别。
不知不觉已经写了这么多字了,是时候结束这篇的内容了,和前一篇一样,在这一篇的最后,会有一个小小的恶作剧代码,说是恶作剧代码,实际上是一个小小的内容预告,在下一篇中,我们将一起学习面向对象编程的另一个重要概念——继承
- class Slime
- def appear; @hp = 10; end
- def die; @hp = 0; end
- def bark
- if @hp == 0
- p 'Uuuu...'
- elsif @hp > 50
- p 'Golden——!'
- else
- p 'GaO!!'
- end
- end
- end
- class Golden_Slime < Slime
- def appear; @hp = 100; end
- end
- enemy = Golden_Slime.new
- enemy.appear
- enemy.bark
复制代码
[groupid=572]喵呜喵5的坑[/groupid]
作者: 正太君 时间: 2016-7-31 08:13
依然是坐等火了之后再帮你移到技术区...(为什么不上后台自己移呢?
作者: taroxd 时间: 2016-7-31 10:37
本帖最后由 taroxd 于 2016-7-31 12:38 编辑
def Slime
this = {
:hp => 10,
:mp => 10,
:tp => rand(5),
:exp => rand(5),
:lv => rand(5),
:atk => rand(5),
:def => rand(5),
:die => lambda { this[:hp] = 0 },
:bark => lambda {
if this[:hp] == 0
puts 'Uuuu...'
else
puts 'GaO!!'
end
}
}
end
slime1 = Slime()
slime1[:bark].call
slime1[:die].call
slime1[:bark].call
def Slime
this = {
:hp => 10,
:mp => 10,
:tp => rand(5),
:exp => rand(5),
:lv => rand(5),
:atk => rand(5),
:def => rand(5),
:die => lambda { this[:hp] = 0 },
:bark => lambda {
if this[:hp] == 0
puts 'Uuuu...'
else
puts 'GaO!!'
end
}
}
end
slime1 = Slime()
slime1[:bark].call
slime1[:die].call
slime1[:bark].call
不用 class 强行实现一下封装、继承、多态这些面向对象的特性w
def new(constructor, *args)
props = {}
this = -> action, *a {
if action == :props
props
else
constructor[this, action, *a]
end
}
constructor[this, :initialize, *args]
this
end
MyObject = -> this, action, *args {
raise NoMethodError.new("undefined method #{action}", action, args)
}
Barkable = -> parent {
-> this, action, *args {
case action
when :bark
if this[:hp] == 0
puts 'Uuuu...'
else
puts 'GaO!!'
end
else
parent[this, action, *args]
end
}
}
Slime = -> parent {
-> this, action, *args {
case action
when :initialize, :set_hp
this[:props][:hp], = args
when :hp
this[:props][:hp]
when :die
this[:set_hp, 0]
else
parent[this, action, *args]
end
}
}[Barkable[MyObject]]
GoldenSlime = -> parent {
-> this, action, *args {
case action
when :initialize
parent[this, action, 100]
when :bark
if this[:hp] > 50
puts 'Golden!'
else
parent[this, action]
end
else
parent[this, action, *args]
end
}
}[Slime]
slime1 = new GoldenSlime
slime1[:bark]
slime1[:die]
slime1[:bark]
def new(constructor, *args)
props = {}
this = -> action, *a {
if action == :props
props
else
constructor[this, action, *a]
end
}
constructor[this, :initialize, *args]
this
end
MyObject = -> this, action, *args {
raise NoMethodError.new("undefined method #{action}", action, args)
}
Barkable = -> parent {
-> this, action, *args {
case action
when :bark
if this[:hp] == 0
puts 'Uuuu...'
else
puts 'GaO!!'
end
else
parent[this, action, *args]
end
}
}
Slime = -> parent {
-> this, action, *args {
case action
when :initialize, :set_hp
this[:props][:hp], = args
when :hp
this[:props][:hp]
when :die
this[:set_hp, 0]
else
parent[this, action, *args]
end
}
}[Barkable[MyObject]]
GoldenSlime = -> parent {
-> this, action, *args {
case action
when :initialize
parent[this, action, 100]
when :bark
if this[:hp] > 50
puts 'Golden!'
else
parent[this, action]
end
else
parent[this, action, *args]
end
}
}[Slime]
slime1 = new GoldenSlime
slime1[:bark]
slime1[:die]
slime1[:bark]
作者: 威风镰鼬 时间: 2016-7-31 12:03
习得用哈希写方法ww
作者: 鑫の尘埃 时间: 2016-7-31 13:00
/w\又屠杀史莱姆了
作者: 御曹司 时间: 2016-7-31 13:11
ぷよぷよ?
作者: chd114 时间: 2016-7-31 16:06
什么?让史莱姆进入我们的巢穴?
作者: 落雪鸦杀 时间: 2016-7-31 17:28
开始懵了……
尤其是提到hash那里就完全不会了……
最底下那段代码的输出应该是?
不太懂专业说法,但是Golden_Slime这个类是Slime类的子集这样的感觉?
作者: shitake 时间: 2016-7-31 22:23
taroxd 发表于 2016-7-31 10:37
def Slime
this = {
:hp => 10,
如果黑一下hash的method_missing就更好了 233
class实际上也只是种包含了method的更复杂的数据结构罢了 lua面向对象的实现就是靠的table 而js则依托于object 然而这些和ruby的hash都是类似的东西
作者: myownroc 时间: 2016-8-1 23:11
面向对象的道理我都懂,然而我就是习惯面向过程怎么办╮(╯▽╰)╭
作者: 七重 时间: 2016-8-5 00:34
可以請教一下吗。。假如想要写史莱姆们的生命值是共用的话,按照对象化的思想应该是怎么样写呢?
我只会用@hp = $hp这种低级的写法。。。
#像是rpg游戏里面,高级的粘液怪物假如不全部消灭的话又会不断再生。
#那么假想出一种所有史莱姆共用一条生命值的情况。
$hp = 10 #所有史莱姆共用10血
class Slime
def appear; @hp = $hp; end #即使是新出现的史莱姆,生命值也是和共用生命值相同的。
def die; @hp = 0 $hp = 0; end
def bark
if @hp == 0
p 'Uuuu...'
else
p 'GaO!!'
end
end
end
#像是rpg游戏里面,高级的粘液怪物假如不全部消灭的话又会不断再生。
#那么假想出一种所有史莱姆共用一条生命值的情况。
$hp = 10 #所有史莱姆共用10血
class Slime
def appear; @hp = $hp; end #即使是新出现的史莱姆,生命值也是和共用生命值相同的。
def die; @hp = 0 $hp = 0; end
def bark
if @hp == 0
p 'Uuuu...'
else
p 'GaO!!'
end
end
end
作者: 白风少侠 时间: 2016-8-5 06:47
{:2_249:}顶一个
作者: taroxd 时间: 2016-8-5 07:47
class Slime
@hp = 10
class << self
attr_accessor :hp
end
def hp
Slime.hp
end
def appear
Slime.hp = 10
end
def die
Slime.hp = 0
end
def bark
if hp == 0
# ...
end
end
end
class Slime
@hp = 10
class << self
attr_accessor :hp
end
def hp
Slime.hp
end
def appear
Slime.hp = 10
end
def die
Slime.hp = 0
end
def bark
if hp == 0
# ...
end
end
end
作者: www19910226 时间: 2016-8-5 11:06
面向对象的道理我都懂,然而我就是习惯面向过程怎么办
欢迎光临 Project1 (https://rpg.blue/) |
Powered by Discuz! X3.1 |