iT邦幫忙

2021 iThome 鐵人賽

DAY 6
0
自我挑戰組

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

D-24. attr_accessor 、類別變數與實體變數差異 && Minimum Moves to Equal Array Elements

本篇有一個區塊的code,是一些常見問題在code中的長相以及用法,裡面有一個非常陽春的名字檢測器,可以利用運行時,使用者輸入的名字來檢測。
code中會看到兩種符號#**@@

#**部分就是常見問題。
請說明attr_accessor 今天內容有
類別變數,實體變數差異? 今天內容有
Ruby中self的意思?
請說明類別方法or實體方法差異
Ruby中private的用法

#@@部分只是單純想分享。
initialize與new的差別? 今天內容有
參數加等號。 今天內容有
yield if block_given? 今天內容有
腳本區會一起說到以下內容gets.chomp()sleep的用途
外部腳本可不可以直接改物件的資料。

如果願意,可以執行看看,單純想看常見問題,可直接略過。

class Elden_ring
  attr_accessor :name, :role, :power  #**

  @@bad_word = ["髒", "壞", "衰", "呵"]  #**

  def initialize(name = "殭屍", role = "反派", power = 20) #@@
    @name = name
    @role = role
    @power = power
  end

  def named
    yield if block_given? #@@
    new_name = gets.chomp() #@@
    if bad_name?(new_name)
      puts "名字含有不好的字元或空白,請重新輸入"
      named() #@@
    else
      self.name = new_name #**
      puts %{是的! 原來他叫"#{self.name}"!} #@@
    end
  end

  # class << self  #類別方法  #**
  #   def fake_new
  #     Elden_ring.new("隱藏大魔王", "玩家", "unlimited")
  #   end
  # end

  # private  #**
  def bad_name?(new_name)
    ((new_name.split"") & @@bad_word) != [] || new_name == ""
  end
end

##下面是腳本區。 也可將兩個區塊分開,利用`require`來運行

gwyn = Elden_ring.new("葛溫", "薪王", "is Over 9000!")
ash = Elden_ring.new("不死鎮雜魚", "戰士")

ash.bad_name?(ash.name) #測試private用

puts "#{gwyn.name},祂的職業是#{gwyn.role}。
他的力量#{gwyn.power}"

sleep 2 #@@

puts "\n有一天,有一個#{ash.name}。","是一個力量只有#{ash.power}的#{ash.role}雜魚。"

sleep 2

puts %{\n#{ash.name}它身為雜魚,平時非常羨慕"#{gwyn.name}"的存在
它希望自己能像"#{gwyn.name}"一般,成為'神族的一員'!
}

sleep 2

puts "%s\n%s" % ["這天,它做出了雜魚不可能會出的一件事!", "它坐下冥想...."]
sleep 2

["一年過去了...", "兩年過去了...", "三年...", "四年...", "整整五年的時間。"].each{|str|puts str; sleep 2}

ash.named do
  prompt = '> '
  puts "它想起了它擁有一個古老的名字!"
  puts %{#請幫忙輸入新名字#}
  print prompt
end

sleep 2
puts "感謝參與測試!"

# boss = Elden_ring.fake_new  #測試類別方法用

# puts boss.name

# ash.fake_new

attr_accessor

是一個Module類別定義出來的方法,由於許多類別常需有取得器(getter)設定器(setter)的需求,以方便操作改寫資料與讀取,所以直接設計attr_accessor方法,不是取代getter與setter,是會直接幫你生成這兩項的相對應方法,常直接寫在類別上方,也更清楚這類別中有哪些資料可以取得及設定。

如果自己設定gettersetter,名字隨意取也可以,但依照慣例盡量取相同例如下方示範,def name就給@name,這習慣到Rails後也是,保持這習慣,開發速度也會比較快。
不用眼睛花掉還找不到當初自己設定的名字是什麼!

用途?

#取代getter與setter
class Fake_class
  def initialize(name = "金三角")
    @name = name
  end

  def name   #getter methods,取得物件內資料。
   @name
  end
  
  def name=(new_name) #setter methods,設定物件內資料。
   @name = new_name
  end

  def change_name(name)  #有setter 這類方法才能執行
   @name = name
  def
end

2.7.3 :052 > abc = Fake_class.new
 => #<Fake_class:0x00007f923942cc28 @name="金三角">
2.7.3 :053 > abc.name
 => "金三角"
2.7.3 :054 > abc.change_name("泰山")
 => "泰山"
2.7.3 :055 > abc.name
 => "泰山"

使用attr_accessor後,就可以少設定

class Fake_class
  attr_accessor :name

  def initialize(name = "金三角")
    @name = name
  end
end

irb輸入完後可以輸入.methods,可以看到:name 與:name= 方法。

attr_writer=> 只生成setter
attr_reader=> 只生成getter

如果可以,不要因為懶惰而都只用attr_accessor,有些資料確定只能讀取,而不希望被改變就使用attr_reader,有些資料可以更動,但不希望被讀取那就attr_writer,雖然進入框架後(不只是Rails),可能也不會再需要手動設定這些,但是知道自己哪些資料該怎麼處理的觀念還是要有。


類別變數,實體變數差異?

不確定變數有幾種,可以請看看龍哥的,為你自己學Ruby on Rails:變數(variable)

少用類別變數

這是實體變數@variable

class Evil_soul
  @name = "惡魔靈魂"

  def self.name
    p "#{@name}!"
  end
end

2.7.3 :074 > Evil_soul.name
"惡魔靈魂!"
 => "惡魔靈魂!"

#建立一個子類別
class Dark_soul < Evil_soul
  @name = "黑暗靈魂"
end

2.7.3 :078 > Dark_soul.name
"黑暗靈魂!"
 => "黑暗靈魂!"

兩個類別的@name不會互相干擾
即使繼承後也一樣。

2.7.3 :079 > Evil_soul.name
"惡魔靈魂!"
 => "惡魔靈魂!"

如果使用類別變數@@variable

class Evil_soul
  @@name = "惡魔靈魂"

  def self.name
    p "#{@@name}!"
  end
end

2.7.3 :087 > Evil_soul.name
"惡魔靈魂!"
 => "惡魔靈魂!"

class Dark_soul < Evil_soul
  @@name = "黑暗靈魂"
end

#一被繼承,如果新類別也有用這個類別變數,類別變數馬上改變。
2.7.3 :091 > Evil_soul.name
"黑暗靈魂!"
 => "黑暗靈魂!"

少用類別變數比較好,避免類別被繼承後,類別變數會馬上改變,因為類別變數在同一條繼承鏈上所有的類別是共用的。實體變數作用在單一類別內,而且兩實體間互相不受影響。
當然更不適合直接用全域變數,這只是測試,我只放四個字,如果是很多內容的資料,應該放在別處。


initializenew的差別?

先設定一個class。

class Init_test
  def some(name = "測試", methods ="200種")
    @name = name
    @methods = methods
  end
end
#這個類別new之後,只有給記憶體編號。
2.7.3 :118 > test = Init_test.new
 => #<Init_test:0x00007feb2a377dd8>
2.7.3 :068 > Init_test.some
Traceback (most recent call last):
NoMethodError (undefined method `some' for Init_test:Class)
#這樣設定some是實體方法,Init_test用不了。

可以發現,故意設定一種內容跟initiailize方法,是毫無反應的。

class Init_test
  def initialize(name = "測試", methods ="200種")
    @name = name
    @methods = methods
  end
end
2.7.3 :126 > test = Init_test.new
 => #<Init_test:0x00007feb2a30e1f8 @name="測試", @methods="200種">

用initialize才有除了記憶體編號外,有其他"數值"存在。
也代表initialize與new有"聯動"。

#如果這樣是會動。
class Init_test
  def new(name = "測試", methods ="200種")
    @name = name
    @methods = methods
  end
end
2.7.3 :133 > Init_test.new
 => #<Init_test:0x00007feb2a366948 @name="測試", @methods="200種">

但這樣等於你把Ruby原本設定好的new方法更動了,請不要這樣。

new方法使用時,會自動調動initialize方法,利用初始化來,建構物件的"長相",所以我們更動initialize就會自動調整到new方法。而如果直接動new方法,那你得先確認new方法裡會不會有你不知道的內容了。

initialize在其他語言是建構子的概念(英語: Constructor,有時簡稱 ctor)


參數加等號 == 參數預設值

def some_method(num = "123")
  num.to_i
end

這樣代表如果我們沒輸入參數,會自動以預設值處理。

2.7.3 :099 > some_method()
 => 123
2.7.3 :100 > some_method(567)
 => 567
#進入有連接資料庫的階段後,要更注意資料欄位的Type,以及自己設定的方法之使用對象。
2.7.3 :111 > some_method([1, 2, 3])
Traceback (most recent call last):
NoMethodError (undefined method `to_i' for [1, 2, 3]:Array)

名字檢驗器

雖然陽春,還是有幾個小小小細節說明一下。
原本code長這樣。

def named
    prompt = '> '
    puts "它想起了它擁有一個古老的名字!"
    puts %{#請幫忙輸入新名字#}
    print prompt
    new_name = gets.chomp()
    if ((new_name.split"") & $bad_word) != [] || new_name == ""
      puts "名字含有不好的字元或空白,請重新輸入"
      named()
    else
      self.name = new_name
      puts %{是的! 原來他叫"#{self.name}"!}
    end
  end

  #下面這四行,不是沒有意義,但我寫在方法裡,等於寫死了台詞。
  prompt = '> '
  puts "它想起了它擁有一個古老的名字!"
  puts %{#請幫忙輸入新名字#}
  print prompt

  #而我使用yield出去後,如果我想換台詞,只需要在腳本區內用到的地方,其block區更改內容即可。
  object.named {puts "這樣便利多了"}

yield if block_given?named方法又出現了named()

方法內再執行一次同方法算是常見手法,等於是跑迴圈的一種,可以不用再看到loop,while,for..in。用多了會發現很好用。

yield if block_given因為我只是請方法再幫我執行一次方法,我第二次並沒有給予block。
yield就是去block
要提這個是與rails裡的錯誤處理機制一樣,屬於比較柔性的處理方式。

回到code。

  ((new_name.split"") & $bad_word) != [] || new_name == ""

常用會用到的行為,不如直接多建立一個方法。
大家都會用到的行為,不如直接建立一個模組。

所以直接建立了一個bad_name?()

  def bad_name?(new_name)
    ((new_name.split"") & @@bad_word) != [] || new_name == ""
  end

可以看到整個檢驗器非常簡單,不是因為我檢測資料只有4個字,是因為功能只有拆開輸入的字串,再去與檢測資料比對,而如果是希望檢測名字內有沒有敏感的詞句,或敏感時事人物名字時,又該如何處理。
那當然是我另外一篇文章的事了

明日從self繼續


今日的Leetcod.453. Minimum Moves to Equal Array Elements
題目連結:https://leetcode.com/problems/minimum-moves-to-equal-array-elements/
題目重點:一次可以動n-1個元素來加1(今天是單純數學題。)

# @param {Integer[]} nums
# @return {Integer}
def min_moves(nums)

end

puts min_moves([1,2,3]) #=> 3
puts min_moves([1,1,1]) #=> 0

煉金術士題..

sum = 初始所有數組和
n = 數組長度
m = 增加次數
x = m次完後會等於的數字
x * n = 最後數組的總和
min = 數組中最小的數
關係如下
sum + m*(n - 1) = x * n
由於增加數都是1,所以最後會等於的數字x = min + m,帶入上一行公式。
sum + m*n - m = min*n + m*n
sum - m = min*n
m現在是我們唯一不知道的
sum - min*n = m
解完!

def min_moves(nums)
  nums.sum - (nums.min * nums.size)
end


今天重點
1.說明attr_accessor
2.類別變數,實體變數差異?
3.Leetcod.453. Minimum Moves to Equal Array Elements


上一篇
D-25. 枚舉(enumerate) && Intersection of Two Arrays II
下一篇
D-23. Self的意思、實體方法與類別方法、Private方法 && Minimum Moves to Equal Array Elements II
系列文
初級紅寶石魔法師心得分享。30

尚未有邦友留言

立即登入留言