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.史莱姆巢穴



  1. class Slime
  2.   def appear; @hp = 10; end
  3.   def die; @hp = 0; end
  4.   def bark
  5.     if @hp == 0
  6.       p 'Uuuu...'
  7.     else
  8.       p 'GaO!!'
  9.     end
  10.   end
  11. end
  12. enemy1 = Slime.new
  13. enemy2 = Slime.new
  14. enemy1.appear
  15. enemy2.appear
  16. enemy1.bark
  17. enemy2.bark
  18. enemy1.die
  19. enemy1.bark
  20. enemy2.bark
  21. enemy3 = Slime.new
  22. enemy3.appear
  23. enemy3.bark
  24. # Slime - 史莱姆
  25. # appear - 出现
  26. # die - 死亡
  27. # bark - 吠叫
  28. # enemy - 敌人
复制代码

请不要借助任何代码执行工具,阅读上面的代码并思考,输出的结果是什么?



这次的教程我决定从一个传统的日式 RPG 故事作为开始:
一个新人冒险者接到了他冒险生涯中的第一个任务——讨伐三只史莱姆,从公会的接待那里领到了史莱姆的图鉴后,他兴致勃勃的踏上了旅途,没走多远,两个敌人嘎哦嘎哦的叫着从草丛中出现了……
“嘿,这就是史莱姆,他们的行为举止果然和图鉴上写的一模一样!”
一边注意着周围的环境,冒险者打倒了了其中一只史莱姆
“真是绝情啊,明明自己的同伴已经死亡了,另外这只史莱姆却仿佛什么都没发生一样的”
冒险者一边看着发出呜呜的声音瘫软在路边的史莱姆,一边开始将集中精力应对起剩下的那只史莱姆
这时候,从冒险者的身后,第三只史莱姆精力充沛的叫着嘎哦出现了!

从1967年第一种面向对象的语言 Simula67 发明到你阅读这篇教程的这个时间节点,编程语言已经发生了太多的改变,不论是内存控制亦或是垃圾收集,多亏了编程语言和硬件性能的发展,我们得以从各种乏味单调的任务中解脱从而将精力放到运行逻辑本身。鉴于继续顺着这个话题说下去估计这篇教程的主题就回不来了,我们就此打住,用代码说话
回到我们最开始的故事,冒险者公会将史莱姆写成图鉴,史莱姆的行动和图鉴一模一样,史莱姆1死了以后,史莱姆2仿佛什么都没变,史莱姆3在史莱姆1死了以后仍然精力充沛的出现,这就是面向对象的特征
下面,我们可以从头开始完整的分析一遍代码了,我们一起从上往下看一下

  1. class Slime
  2.   def appear; @hp = 10; end
  3.   def die; @hp = 0; end
  4.   def bark
  5.     if @hp == 0
  6.       p 'Uuuu...'
  7.     else
  8.       p 'GaO!!'
  9.     end
  10.   end
  11. end
复制代码


  1. enemy1 = Slime.new
  2. enemy2 = Slime.new
  3. enemy1.appear
  4. enemy2.appear
  5. enemy1.bark
  6. enemy2.bark
  7. enemy1.die
  8. enemy1.bark
  9. enemy2.bark
  10. enemy3 = Slime.new
  11. enemy3.appear
  12. enemy3.bark
复制代码


这段代码运行后的结果是
  1. "GaO!!"
  2. "GaO!!"
  3. "Uuuu..."
  4. "GaO!!"
  5. "GaO!!"
复制代码

在上一篇中,我们曾经说到,对于面向编程来说,每一个实例都是不同的,就好比这里的敌人1、敌人2、敌人3是三只不同的史莱姆一样,那么,这三只史莱姆明明行为都是一样的,为什么却需要让他们不同呢,这里就引出了各个实例“状态”的作用,在这次的例子里,就是三只史莱姆的HP,也就是代码中出现的 @hp 的功能。正因为像这种以 @ 开头的变量是用来表明实例状态的,所以我们称呼他为“实例变量”
实例的变量的存在让一个个实例的状态不再相同,在 class Slime 中,我们定义了史莱姆的行为,在实例 enemy1、enemy2、enemy3 中,我们储存了史莱姆的状态,从冒险者的角度来看,史莱姆在做的事情仅仅只是吠叫而已,冒险者无法关注到史莱姆吠叫时的 if else 逻辑,冒险者也没有必要去关注史莱姆吠叫时的 if else 逻辑。将不确定因素进行封装来让代码易于理解,这正是面向对象的编程所期望的
当然,虽然吹了半天面向对象,但是没有面向对象并不代表你就写不出代码了,例如,上面那段代码完全可以用下面这段面向过程的代码来等效替代
  1. def appear; return 10; end
  2. def die; return 0; end
  3. def bark(hp)
  4.   if hp == 0
  5.     p 'Uuuu...'
  6.   else
  7.     p 'GaO!!'
  8.   end
  9. end
  10. enemy1 = 'Slime'
  11. enemy2 = 'Slime'
  12. enemy1_hp = appear
  13. enemy2_hp = appear
  14. bark(enemy1_hp)
  15. bark(enemy2_hp)
  16. enemy1_hp = die
  17. bark(enemy1_hp)
  18. bark(enemy2_hp)
  19. enemy3 = 'Slime'
  20. enemy3_hp = appear
  21. bark(enemy3_hp)
复制代码
如果场上同时出现三只史莱姆时,两种编程方式只从行数上来看似乎相差也不大……
  1. enemy1 = Slime.new
  2. enemy1.appear
  3. enemy2 = Slime.new
  4. enemy2.appear
  5. enemy3 = Slime.new
  6. enemy3.appear
复制代码
  1. enemy1 = 'Slime'
  2. enemy1_hp = appear
  3. enemy2 = 'Slime'
  4. enemy2_hp = appear
  5. enemy3 = 'Slime'
  6. enemy3_hp = appear
复制代码
但是,当每只史莱姆除了拥有HP这个状态,还拥有 MP、TP、EXP、LV、ATK、DEF 这些状态的时候……
(@atk 论坛 @ bug修正)
  1. class Slime
  2.   def appear
  3.     @hp = 10
  4.     @mp = 10
  5.     @tp = rand(5)
  6.     @exp = rand(5)
  7.     @lv = rand(5)
  8.     @atk = rand(5)
  9.     @def = rand(5)
  10.   end
  11.   def die; @hp = 0; end
  12.   def bark
  13.     if @hp == 0
  14.       p 'Uuuu...'
  15.     else
  16.       p 'GaO!!'
  17.     end
  18.   end
  19. end
  20. enemy1 = Slime.new
  21. enemy1.appear
  22. enemy2 = Slime.new
  23. enemy2.appear
  24. enemy3 = Slime.new
  25. enemy3.appear
复制代码
  1. def appear_hp; return 10; end
  2. def appear_mp; return 10; end
  3. def appear_tp; return rand(5); end
  4. def appear_exp; return rand(5); end
  5. def appear_lv; return rand(5); end
  6. def appear_atk; return rand(5); end
  7. def appear_def; return rand(5); end
  8. def die; return 0; end
  9. def bark(hp)
  10.   if hp == 0
  11.     p 'Uuuu...'
  12.   else
  13.     p 'GaO!!'
  14.   end
  15. end
  16. enemy1 = 'Slime'
  17. enemy1_hp = appear_hp
  18. enemy1_mp = appear_mp
  19. enemy1_tp = appear_tp
  20. enempy1_exp = appear_exp
  21. ………………$%$^%$@#$@
复制代码
当然,你可能会利用 hash 把你的代码写成这样
  1. def appear
  2.   return {
  3.     :hp => 10,
  4.     :mp => 10,
  5.     :tp => rand(5),
  6.     :exp => rand(5),
  7.     :lv => rand(5),
  8.     :atk => rand(5),
  9.     :def => rand(5),
  10.   }
  11. end
  12. def die; return 0; end
  13. def bark(hp)
  14.   if hp == 0
  15.     p 'Uuuu...'
  16.   else
  17.     p 'GaO!!'
  18.   end
  19. end
  20. enemy1 = 'Slime'
  21. enemy1_state = appear
  22. enemy2 = 'Slime'
  23. enemy2_state = appear
  24. enemy2 = 'Slime'
  25. enemy2_state = appear
复制代码
那么,恭喜你,你已经在很大程度上理解了面向对象编程所做的事情是什么了哦,在你的代码里 enemy1_state[:hp] 对应的就是面向编程中 enemy1 实例的实例变量 @hp 所承担的作用
如果你对 Proc 略有了解,更进一步,把你的代码改写成这样
  1. def appear
  2.   die = Proc.new{ 0 }
  3.   bark = Proc.new{|hp|
  4.     if hp == 0
  5.       p 'Uuuu...'
  6.     else
  7.       p 'GaO!!'
  8.     end
  9.   }
  10.   return {
  11.     :hp => 10,
  12.     :mp => 10,
  13.     :tp => rand(5),
  14.     :exp => rand(5),
  15.     :lv => rand(5),
  16.     :atk => rand(5),
  17.     :def => rand(5),
  18.     :die => die,
  19.     :bark => bark
  20.   }
  21. end
  22. enemy1 = 'Slime'
  23. enemy1_state = appear
  24. enemy2 = 'Slime'
  25. enemy2_state = appear
  26. enemy2 = 'Slime'
  27. enemy2_state = appear
复制代码
那么,你的代码已经越来越接近面向对象的思维了呢。实际上,对于类似 lua 或者 javascript 这样的语言,这就是他们面向对象的实现方式。当然,这里要特别说明的是,虽然现阶段面向对象在你眼中可能只是一个语法糖一样的东西,实际上,他却和面向过程存在编程思想上的区别。

不知不觉已经写了这么多字了,是时候结束这篇的内容了,和前一篇一样,在这一篇的最后,会有一个小小的恶作剧代码,说是恶作剧代码,实际上是一个小小的内容预告,在下一篇中,我们将一起学习面向对象编程的另一个重要概念——继承
  1. class Slime
  2.   def appear; @hp = 10; end
  3.   def die; @hp = 0; end
  4.   def bark
  5.     if @hp == 0
  6.       p 'Uuuu...'
  7.     elsif @hp > 50
  8.       p 'Golden——!'
  9.     else
  10.       p 'GaO!!'
  11.     end
  12.   end
  13. end
  14. class Golden_Slime < Slime
  15.   def appear; @hp = 100; end
  16. end
  17. enemy = Golden_Slime.new
  18. enemy.appear
  19. enemy.bark
复制代码

[groupid=572]喵呜喵5的坑[/groupid]
作者: 正太君    时间: 2016-7-31 08:13
依然是坐等火了之后再帮你移到技术区...(为什么不上后台自己移呢
作者: taroxd    时间: 2016-7-31 10:37
本帖最后由 taroxd 于 2016-7-31 12:38 编辑

RUBY 代码复制
  1. def Slime
  2.   this = {
  3.     :hp => 10,
  4.     :mp => 10,
  5.     :tp => rand(5),
  6.     :exp => rand(5),
  7.     :lv => rand(5),
  8.     :atk => rand(5),
  9.     :def => rand(5),
  10.     :die => lambda { this[:hp] = 0 },
  11.     :bark => lambda {
  12.       if this[:hp] == 0
  13.         puts 'Uuuu...'
  14.       else
  15.         puts 'GaO!!'
  16.       end
  17.     }
  18.   }
  19. end
  20.  
  21. slime1 = Slime()
  22. slime1[:bark].call
  23. slime1[:die].call
  24. slime1[:bark].call



不用 class 强行实现一下封装、继承、多态这些面向对象的特性w
RUBY 代码复制
  1. def new(constructor, *args)
  2.   props = {}
  3.   this = -> action, *a {
  4.     if action == :props
  5.       props
  6.     else
  7.       constructor[this, action, *a]
  8.     end
  9.   }
  10.   constructor[this, :initialize, *args]
  11.   this
  12. end
  13.  
  14. MyObject = -> this, action, *args {
  15.   raise NoMethodError.new("undefined method #{action}", action, args)
  16. }
  17.  
  18. Barkable = -> parent {
  19.   -> this, action, *args {
  20.     case action
  21.     when :bark
  22.       if this[:hp] == 0
  23.         puts 'Uuuu...'
  24.       else
  25.         puts 'GaO!!'
  26.       end
  27.     else
  28.       parent[this, action, *args]
  29.     end
  30.   }
  31. }
  32.  
  33. Slime = -> parent {
  34.   -> this, action, *args {
  35.     case action
  36.     when :initialize, :set_hp
  37.       this[:props][:hp], = args
  38.     when :hp
  39.       this[:props][:hp]
  40.     when :die
  41.       this[:set_hp, 0]
  42.     else
  43.       parent[this, action, *args]
  44.     end
  45.   }
  46. }[Barkable[MyObject]]
  47.  
  48. GoldenSlime = -> parent {
  49.   -> this, action, *args {
  50.     case action
  51.     when :initialize
  52.       parent[this, action, 100]
  53.     when :bark
  54.       if this[:hp] > 50
  55.         puts 'Golden!'
  56.       else
  57.         parent[this, action]
  58.       end
  59.     else
  60.       parent[this, action, *args]
  61.     end
  62.   }
  63. }[Slime]
  64.  
  65. slime1 = new GoldenSlime
  66. slime1[:bark]
  67. slime1[:die]
  68. 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那里就完全不会了……

最底下那段代码的输出应该是
  1. "Golden——!"
复制代码
?
不太懂专业说法,但是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这种低级的写法。。。

RUBY 代码复制
  1. #像是rpg游戏里面,高级的粘液怪物假如不全部消灭的话又会不断再生。
  2.  
  3. #那么假想出一种所有史莱姆共用一条生命值的情况。
  4.  
  5. $hp = 10  #所有史莱姆共用10血
  6.  
  7. class Slime
  8.   def appear; @hp = $hp; end  #即使是新出现的史莱姆,生命值也是和共用生命值相同的。
  9.   def die; @hp = 0 $hp = 0; end
  10.   def bark
  11.     if @hp == 0
  12.       p 'Uuuu...'
  13.     else
  14.       p 'GaO!!'
  15.     end
  16.   end
  17. end

作者: 白风少侠    时间: 2016-8-5 06:47
{:2_249:}顶一个
作者: taroxd    时间: 2016-8-5 07:47
七重 发表于 2016-8-5 00:34
可以請教一下吗。。假如想要写史莱姆们的生命值是共用的话,按照对象化的思想应该是怎么样写呢?

我只会用 ...

RUBY 代码复制
  1. class Slime
  2.   @hp = 10
  3.   class << self
  4.     attr_accessor :hp
  5.   end
  6.  
  7.   def hp
  8.     Slime.hp
  9.   end
  10.  
  11.   def appear
  12.     Slime.hp = 10
  13.   end
  14.  
  15.   def die
  16.     Slime.hp = 0
  17.   end
  18.  
  19.   def bark
  20.     if hp == 0
  21.       # ...
  22.     end
  23.   end
  24.  
  25. end

作者: www19910226    时间: 2016-8-5 11:06

面向对象的道理我都懂,然而我就是习惯面向过程怎么办




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