iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
自我挑戰組

初級紅寶石魔法師心得分享。系列 第 8

D-22. 繼承(繼承鏈問題)、模組(extend、include、prepend差異) && Add to Array-Form of Integer

繼承(Inheritance)

class A < B<就是繼承,A繼承B
Ruby是單向繼承,抱歉我用比較粗俗的方式說明,只可以有一個爸爸,但可以有很多小孩。
另外模組不是養父,是爸爸學會了,兒子就會了,模組也不是越多功能越好,能做到幾個相同性質組一個模組最好。還有昨天說明過了,類別方法都會被繼承。

繼承看code就能明白。設定一個類別。

class Protype_monster
  attr_accessor :name, :power
  def initialize(name = "黑龍", power = rand(800..900))
    @name = name
    @power = power
  end

  def final_fight
    final_boss
  end

  protected
  def cant_sleep
    puts "有睡眠抵抗力"
  end

  private
  def final_boss
    puts "我通常在最後一張地圖出現"
  end
end

2.7.3 :022 > millaboreus = Protype_monster.new
 => #<Protype_monster:0x00007f88f941b020 @name="黑龍", @power=889>
 
2.7.3 :023 > p millaboreus
#<Protype_monster:0x00007f88f941b020 @name="黑龍", @power=889>
 => #<Protype_monster:0x00007f88f941b020 @name="黑龍", @power=889>
 
2.7.3 :024 > millaboreus.final_fight
我通常在最後一張地圖出現
 => nil
 
millaboreus.cant_sleep   #NoMethodError:protected方法不能外篰直接使用。
millaboreus.fly          #NoMethodError
millaboreus.environment  #NoMethodError

再來是一個子層。

class Elder_dragon < Protype_monster
  def fly
    puts "滯空#{rand(30..50)}秒"
  end

  def environment
    rand(1..10) > 5 ? (puts "改變環境") : (puts "環境未改變")
  end

  def resistance
    self.cant_sleep
  end
end

ksardaora = Elder_dragon.new("鋼龍")

2.7.3 :042 > p ksardaora
#<Elder_dragon:0x00007f88f9412060 @name="鋼龍", @power=850>
 => #<Elder_dragon:0x00007f88f9412060 @name="鋼龍", @power=850>
 
2.7.3 :043 > puts ksardaora.name
鋼龍
2.7.3 :044 > puts "力量#{ksardaora.power}"
力量850
##子層沒設定的,但父層有,所以繼承沿用,attr_accessor系列,initialize的架構。

2.7.3 :045 > ksardaora.fly
滯空38秒
2.7.3 :046 > ksardaora.environment
環境未改變
#子層的方法,父層無法使用。

ksardaora.final_boss  #=>NoMethodError ,private的實體方法不繼承。
2.7.3 :048 > ksardaora.resistance  ##protected內的可繼承,所以使用方式正確就可用。
有睡眠抵抗力  

可以被很多子類別繼承。

class New_elder_dragon < Elder_dragon
end

class Fly_dragon < Elder_dragon
end

2.7.3 :057 > tigrex = Fly_dragon.new("轟龍", 600)
 => #<Fly_dragon:0x00007f88f93f2e90 @name="轟龍", @power=600>
 
2.7.3 :058 > tigrex.fly
滯空46秒

2.7.3 :059 > tigrex.environment
環境未改變

最後的類別,爸爸跟爺爺會的自己都會,當然這樣不算好設計,最後的怪物變什麼都會。

順便補充了protected
重點在於,單向繼承,利用子類別裡沒有的方法,Ruby會往父層找的功能(父沒有,會繼續往模組以及父的上層找),子類別可以加入其他更多功能,也不會覺得過於龐大。如有同名方法,看是父或子類別呼叫,以用自己的優先。


模組(Module)

Class最大差異在於,模組模組間無法繼承,以及無法New物件,Class中如果引入模組,其子class會繼承到父層引入的模組,但無法模組繼承模組,另外Class的superclass是Module

Mixins:由於Ruby不支援多重繼承,可以以模組來擴充功能,讓子類別越來越完整。

無論Ruby或在Rails上,Module通常會集中放置。

module Elder
  def environment
    rand(1..10) > 5 ? (puts "改變環境") : (puts "環境未改變")
  end
end

module Flight_mode
  def stay_in_the_air
    puts "滯空#{rand(30..50)}秒"
  end
end

module Scared
  def scared
    puts "被閃光彈嚇到會站著不動"
  end
end

module Brute_wyvern
  def run_wild
    puts "朝向玩家衝撞"
  end

  def head_hammer
    puts "使用頭部攻擊"
  end
end

我把一些skill先模組化。

接著重新設計class

class Biology
  include Scared  ##引入模組
  attr_accessor :name, :power
  def initialize(name = "食草龍", power = 10)
    @name = name
    @power = power
  end

  def behavior
    eat()
  end

  private
  def eat
    puts "吃草!"
  end
end

2.7.3 :045 > herbivore = Biology.new
 => #<Biology:0x00007fc9c13318e0 @name="食草龍", @power=10>
 
2.7.3 :046 > herbivore.behavior
吃草!
 => nil
 
2.7.3 :047 > herbivore.scared
被閃光彈嚇到會站著不動
 => nil

初始的class設計成只有基本功能。

接著第二個類別。

class Velkhana < Biology

  include Elder
  include Flight_mode  #模組引入
  
  def initialize(name = "冰咒龍", power = 650)
    @name = name
    @power = power
  end

  def behavior
    eat_snow()
  end

  private
  def eat_snow
    puts "吃雪!"
  end
end

2.7.3 :066 > velkhana = Velkhana.new
 => #<Velkhana:0x00007fc9c1238c18 @name="冰咒龍", @power=650>
 
2.7.3 :067 > velkhana.behavior 
吃雪!
 => nil
 
2.7.3 :068 > velkhana.environment
環境未改變
 => nil
2.7.3 :069 > velkhana.stay_in_the_air
滯空43秒
 => nil
2.7.3 :070 > velkhana.scared
被閃光彈嚇到會站著不動   ## 引入模組會被繼承,所以它也會被嚇到

velkhana.run_wild  #NoMethodError 模組沒引入不能用
velkhana.head_hammer #NoMethodError 模組沒引入不能用

Velkhana.stay_in_the_air  #NoMethodError (undefined method `stay_in_the_air' for Velkhana:Class), include方式導入模組,都是實體方法,類別都不能用。

第三個類別

class Brute_wyverns < Biology
  include Brute_wyvern
  def initialize(name = "惶怒恐暴龍", power = 700)
    @name = name
    @power = power
  end

  def behavior
    boom()
  end

  private
  def boom
    puts "黑色的大黃瓜,超煩人!"
  end
end

2.7.3 :089 > savagedeviljho = Brute_wyverns.new
 => #<Brute_wyverns:0x00007fc9c132af18 @name="惶怒恐暴龍", @power=700>

2.7.3 :090 > savagedeviljho.scared
被閃光彈嚇到會站著不動
 => nil
2.7.3 :091 > savagedeviljho.run_wild
朝向玩家衝撞
 => nil
2.7.3 :092 > savagedeviljho.head_hammer
使用頭部攻擊
 => nil

savagedeviljho.environment  #沒引入模組,不會這些
savagedeviljho.stay_in_the_air  #沒引入模組,不會這些

最後來一個什麼都會的魔王。

class Final_boss < Biology
  extend Elder, Flight_mode, Brute_wyvern
end


2.7.3 :097 > Final_boss.run_wild
朝向玩家衝撞
 => nil
2.7.3 :098 > Final_boss.head_hammer
使用頭部攻擊
 => nil
2.7.3 :099 > Final_boss.environment
改變環境
 => nil
2.7.3 :100 > Final_boss.stay_in_the_air
滯空35秒

順便以這份code顯示了,子與父層如有同名方法,可以自行更改做擴充或改變。

def behavior
  boom()
end

code中也展示了extendinclude的差異。
extend引入的模組方法成為類別方法。
include引入的模組方法成為實體方法。
還有一個prepend下面會說道。


模組同名方法處理

這個不是說引用原則,而是設計上怎麼處理,在設計上難免得同名。
引用才發現同名,請乖乖回頭修改...

module One
  def One.say_hi  #Modlue_name.method_name
   puts "你好喔!"
  end
end

module Two
  def Two.say_hi  #Modlue_name.method_name
   puts "我今天很好喔!"
  end
end

class Hello
  extend One
  extend Two
  def hello
    One.say_hi
    Two.say_hi
  end
end

2.7.3 :060 > Hello.new.hello
你好喔!
我今天很好喔!
 => nil

prepend

有點反向思考,但沒辦法,初期常被問的問題之一。
剛剛我盡量模組寫在上面,這個要反過來寫。

class Hello
  prepend One
  def say_hi
    "你好喔"
  end
end

module One
  def say_hi
    "先呼叫模組的say_hi,再跑到super,也就是父層執行  " + super
  end
end

2.7.3 :125 > Hello.new.say_hi
 => "先呼叫模組的say_hi,再跑到super,也就是父層執行  你好喔"

如果沒super

class Hello
  prepend One
  def say_hi
    "你好喔"
  end
end

module One
  def say_hi
    "先呼叫模組的say_hi,沒了"
  end
end

2.7.3 :166 > Hello.new.say_hi
 => "先呼叫模組的say_hi,沒了"

如果是改用extend``include,就沒效果了。

class Hello
  extend One
  include One
  def say_hi
    "你好喔"
  end
end

module One
  def say_hi
    "先呼叫模組的say_hi,再跑到super,也就是父層執行  " + super
  end
end

2.7.3 :029 > Hello.new.say_hi
 => "你好喔"
2.7.3 :030 > Hello.say_hi
NoMethodError (super: no superclass method `say_hi' for Hello:Class)

簡單點想extend``include``prepend都是能掛載模組,extend引入的模組方法成為類別方法。
include引入的模組方法成為實體方法。prepend能讓class使用super方法,使模組運算完的資料再到自己的方法裡處理。


既然說到了super...

還記得superclass嗎?

2.7.3 :031 > Array.superclass
 => Object

一開始說明繼承的程式碼,已經顯示super是不用寫入,Ruby也會自動使用的,那就看看指定使用的方式。

class Top
  def self.one  #已經開始懶得想方法名了,直接用self代表連new都懶了
    "hello, "
  end
end

class Child < Top
  def self.one
    super + "Player!"
  end
end

2.7.3 :048 > Child.one
 => "hello, Player!"

對,不是模組prepend才能用supersuper本身就是一種方法。

class Super_top
  def self.one
    "OH! "
  end
end

class Top < Super_top
  def self.one
    super + "hello, "
  end
end

class Child < Top
  def self.one
    super + "Player!"
  end
end
2.7.3 :018 > Child.one
 => "OH! hello, Player!"

娛樂性質示範~~


再來,帶參數使用。

class Top
  def math(int)
    int * 10
  end
end

class Child < Top
  def math(int)
    super * 10
  end
end
2.7.3 :012 > Child.new.math(10)
 => 1000

參數是由子傳給父層方法處理。

class Top
  def math(int)
    int = 5
  end
end

class Child < Top
  def math(int)
    int * super
  end
end
2.7.3 :012 > Child.new.math(20)
 => 100

接著是如果不想把參數傳給上層

class Top
  def math
    40
  end
end

class Child < Top
  def math(int)
    int * super()
  end
end
2.7.3 :012 > Child.new.math(20)
 => 800

最後是傳給block

class Top
  def say_hello
    puts "123"
    yield
  end
end

class Child < Top
  def say_hello
    puts "456"
    super
  end
end

2.7.3 :015 > Child.new.say_hello {puts "789"}
789
123
456

呼...

記得require
自己製作的模組不像Math,雖然Math也是模組,但已經成為Ruby的標準函式庫了。
可以直接以Module_name.Moudle_method_name這樣呼叫

2.7.3 :002 > Math.sqrt(16)
 => 4.0

而其實還是可以引入,直接讓irb的空白物件掛載。

:001 > sqrt(16)  #這樣不行
NoMethodError (undefined method 'sqrt' for main:Object)

:002 > include Math  #這樣後就可以了
 => Object
:003 > sqrt(16)
 => 4.0

第八天的leetcode989. Add to Array-Form of Integer
題目連結:https://leetcode.com/problems/add-to-array-form-of-integer/
題目重點:很簡單,手續比較多而已。

# @param {Integer[]} num
# @param {Integer} k
# @return {Integer[]}
def add_to_array_form(num, k)

end

puts add_to_array_form([1,2,0,0], 34) #=>[1, 2, 3, 4]
puts add_to_array_form([2,7,4], 181) #=>[4, 5, 5]
puts add_to_array_form([2,1,5], 806) #=>[1, 0, 2, 1]
puts add_to_array_form([9,9,9,9,9,9,9,9,9,9], 1) #=>[1,0,0,0,0,0,0,0,0,0,0]

第一個例子其實就把答案說完了。

[1, 2, 0, 0] #=> 1200
 :085 > [1, 2, 0, 0].join.to_i
 => 1200

1200 + 34 # Array.join.to_i + k
1234 #=> [1, 2, 3, 4]
:088 > 1234.to_s
 => "1234"
:090 > "1234".split""
 => ["1", "2", "3", "4"]
:091 > ["1", "2", "3", "4"].map{|num| num.to_i}
 => [1, 2, 3, 4]

整理完

def add_to_array_form(num, k)
  (num.join.to_i + k).to_s.split("").map(&:to_i)
end

今天提到的
1.類別繼承
2.Module
3.super方法
4.leetcode989. Add to Array-Form of Integer

明天會解釋&``monkey patch
Ruby部分就介紹完嚕!

頭一個禮拜發現篇幅過大,重點容易丟失,之後就都是一個Rails問題跟一題Leetcode了。


上一篇
D-23. Self的意思、實體方法與類別方法、Private方法 && Minimum Moves to Equal Array Elements II
下一篇
D-21. & 、meta programming & Monkey patch
系列文
初級紅寶石魔法師心得分享。30

尚未有邦友留言

立即登入留言