iT邦幫忙

2021 iThome 鐵人賽

DAY 15
0
Modern Web

初階 Rails 工程師的養成系列 第 15

Day15. Inheritance & Super - Ruby 繼承 part2

Day2 提到過,Ruby為單一繼承的語言。若我們要實現多重繼承的話,我們在 Day14 提到可以使用mixin。今天要介紹的是 Ruby程式語言內,Class 級別的繼承

# 後台退貨單controller
module Admin
  class ReturnOrdersController < ApplicationController
    # Day14 提到的Datatable module
    include Datatable
  end
end


class Admin::ApplicationController < ApplicationController
  # admin 後台動作
end

Rails的專案中,我們一般會在後台管理介面的內容放在admin,並給予NameSpaceAdmin

從上方的關係我們看到,Admin::ReturnOrdersController 繼承了 Admin::ApplicationController,而 Admin::ApplicationController 則繼承了ApplicationController。我們用以下表格說明彼此的權責分工

Class in Controller 說明
Admin::ReturnOrdersController 後台退貨單 Controller
Admin::ApplicationController 後台管理介面相關的 Controller 的動作
ApplicationController 所有 Controller 的動作

Admin::ReturnOrdersControllerancestors 一共有下面這些。

Admin::ReturnOrdersController.ancestors

#=> [Admin::ReturnOrdersController, Datatable, Admin::ApplicationController, #<Module:0x00007fe5cf522800>, ApplicationController, ..., Object, ..., Kernel, BasicObject]

我們分別可以看到 Admin::ReturnOrdersController, Datatable, Admin::ApplicationController, ApplicationController 在繼承鍊的位置。

Inheritance

我們用 ancestors 的繼承鍊,可以看到繼承關係如下!

class A 
end

class B < A
end

class C < B
end

C.ancestors 
#=> [C, B, A, Object, Kernel, BasicObject]

我們習慣會稱 B為A的子類別SubClass、C為B的子類別。如果看到外國文章提到SubClass,記得就是指這種關係。

#inherited

在使用繼承的方法時,可以使用#inherited hook,我們可以在#inherited 新增實體方法跟類別方法。

class A 
  def self.inherited(subclass)
    puts "Singleton class just got subclassed by #{subclass}"
    
    #=> add instance method
    subclass.class_eval do
    end
    
    #=> add class method
    subclass.instance_eval do
    end
  end
end

class B < A
end
#=> Singleton class just got subclassed by B

super

當我們使用了super的關鍵字,就可以取得繼承鍊上一層的內容。換句話說,若我們在繼承鍊寫super,則可以避免覆寫方法。

下列例子中,StrongWarrior 繼承了戰士的屬性。

module Sword
  mattr_accessor :material, default: :silver
  
  def sword_name
    [material.capitalize, 'Sword'].join(' ')
  end
end

class Warrior
  include Sword
  attr_accessor :height, :weight, :tag
  
  def initialize(height = nil, weight = nil)
    @weight = weight
    @height = height
  end
  
  def warrior_info
    {
      height: height,
      weight: weight,
      tag: tag
    }
  end
end

super 不帶參數,代表概括繼承。首先,我們使用沒有參數的super

class StrongWarrior < Warrior
  def initialize(height, weight)
    super
    @tag = 'Strong'
  end
end

warrior = StrongWarrior.new(172, 72)
warrior.warrior_info   #=> {:height=>172, :weight=>72, :tag=>"Strong"}

super帶參數的用法也可以

class WeekWarrior < Warrior
  def initialize(height, weight)
    super(height)
    @tag = 'Week'
  end
end

warrior = WeekWarrior.new(172, 72)
warrior.warrior_info  #=> {:height=>172, :weight=>nil, :tag=>"Week"}

帶入空括號,代表不帶參數進去。

class NilWarrior < Warrior
  def initialize(height, weight)
    super()
    @tag = 'Week'
  end
end

warrior = NilWarrior.new(172, 72)
warrior.warrior_info  #=> {:height=>nil, :weight=>nil, :tag=>"Week"}

⭐️ 由此可以知道,super, super()是完全不一樣的概念

super可以在SubClass的各種方法內使用。

class SuperWarrior < Warrior  def initialize(height, weight)    super    @tag = 'Week'  end    def warrior_info    { **super, a: 1, b: 2 }   endendwarrior = SuperWarrior.new(172, 72)warrior.warrior_info  #=> {:height=>172, :weight=>72, :tag=>"Week", :a=>1, :b=>2}

SuperWarrior#warrior_info 的概念是將原本的Warrior#warrior_info額外填寫其他資料。

此外,Super可以搭配yield來使用喔!

class Warrior
  attr_accessor :height, :weight, :tag
  
  def initialize(height = nil, weight = nil)
    @weight = weight
    @height = height
  end
  
  def foo
    block_given? ? yield : 'foo'
  end
end

class YieldWarrior < Warrior
  def initialize(height, weight)
    super
    @tag = 'Yield'
  end
  
  def foo
    [super {"I am a good boy"}, "am I?"].join(', ')
  end
end

warrior = YieldWarrior.new(172, 72)
warrior.foo  #=> "I am a good boy, am I?"

繼承的基本概念是這樣,接著我們講Bonus的主題 ➡️ 如何取得該類別的各種方法

methods tips and tricks

Day10-15 中,已經介紹了幾個方法

ancestors
instance_methods
singleton_methods

我們可以參考以下的方式篩選查看方法

User.instance_methods
#=> [:name_in_email, :email_upcase, ..., ..., ...] 

#======= false ➡️ 不包含 ancestors 的方法 =======#
User.methods(false)
User.instance_methods(false)

#======= 用減集合來排除多餘的方法 =======#
User.methods - ActiveRecord::Base.methods

#======= grep =======#
string = "This is my IT challenge for 15th day."
string.methods.grep(/.!/).sort

#======= 取得 ancestors =======#
A.singleton_methods - A.singleton_methods(false)

結論

這兩天分別講到module, class級別的繼承,大致上已經介紹了一個段落。

明天會開始介紹常用的設計流程

參考資料


上一篇
Day14. Module & #extend #prepend #include - Ruby 繼承 part1
下一篇
Day16. Service, Strategy and Singleton Pattern
系列文
初階 Rails 工程師的養成34
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言