Day14-15 一共会介绍 Ruby
的2类、4种继承方式。
在Day2 我们提到 Ruby 为单一继承的语言,若想要实现多重继承,可以使用mixin的方式达到相似的效果。
class Warrior
include Sword
include Shield
end
如上程序码所示,上方的Warrior
使用了 include
继承了Sword, Shield
里面所有的方法。除了include
以外,还有prepend
, extend
两种方式可以继承。使用module
继承的方式我们称作 mixin!
以下为今天会介绍的内容
module
Module 的其中一个功能可以做为路径NameSpace
,我们用2个例子说明
#=========== 例1 ===========#
class Admin::Order
end
# 可以写成以下形式
module Admin
class Order
end
end
#=========== 例2 ===========#
class Admin::Order < Admin::Base
end
# 可以写成以下形式
module Admin
class Order < Base
end
end
Admin::Order #=> Admin::Order
当我们搜寻Admin::Order
,能够找到刚刚宣告的类别,而::
为路径的概念。为了说明::
的使用方法,我们以更复杂的例子来说明
module Taipei
class Car
TITLE = '北部车款介绍'
# 厂牌: 在这里定义常数
module Brand
FORD = 0
TESLA = 1
end
def self.drive
'boo boo'
end
end
end
Taipei::Car::TITLE #=> "北部车款介绍"
Taipei::Car::Brand::TESLA #=> 1
Taipei::Car.drive #=> "boo boo"
接着,我们开始讲module
层级的继承
include
是最被广泛使用,也是最简单的继承方式,以下我们先说明include
的用法。
module Sword
mattr_accessor :material, default: :silver
def sword_name
[material.capitalize, 'Sword'].join(' ')
end
end
class Warrior
include Sword
end
warrior = Warrior.new
warrior.sword_name #=> "Silver Sword"
warrior.material = 'Metal'
warrior.sword_name #=> "Metal Sword"
战士的剑预设为银剑,透过setter
将材质改为铁,变为铁剑。
关於 mattr_accessor
的用法可以参考Ruby文件,在这边我们就把它当作 Day12 提到的attr_accessor
,我们在Day13提到,实体方法不一定要在initialize
里面宣告,这边的实体变数定义在module
里面,预设为silver
。
在我其中一个Rails
专案中,有很多个controller
的index
的行为都是呈现 DataTable 的组成表单。因此我把共同的行为都写在module DataTable
,待需要时就include
进来,就不用每使用一个Datatable
就写那麽多程序码。
接着我们来探讨,include
的覆写与继承问题。下方的例子为在class
定义了sword_name
覆写了原本的sword_name
,这种覆写的行为不管在使用继承还是mixin
都称为monkey patch
。
module Sword
mattr_accessor :material, default: :silver
def sword_name
[material.capitalize, 'Sword'].join(' ')
end
end
class Warrior
include Sword
def sword_name
'倚天剑'
end
end
warrior = Warrior.new
warrior.sword_name #=> "倚天剑"
除了覆写以外,我们能不能保留原本的行为?使用include
的答案是不行,但我们可以透过其他方式调用!
module Sword
mattr_accessor :material, default: :silver
def sword_meterial
meterial
end
end
class Warrior
include Sword
def sword_name
[sword_meterial.capitalize, '剑'].join(' ')
end
end
warrior = Warrior.new
warrior.material = '长'
warrior.sword_name #=> "长剑"
使用include
我们无法保持原本的行为,但透过接下来我们要介绍的prepend,可以使用#super
来达到我们要的目的。
prepend
和include
一样,我们可以透过prepend
继承实体方法,不过prepend
却有一些用法使得可以和include
区隔。
prepend
的词意为前面,在继承链中代表着会覆盖原本class
的方法。首先,我们来看prepend
是如何覆盖原本的类别方法
module Sword
mattr_accessor :material, default: :silver
def sword_name
[material.capitalize, 'Sword'].join(' ')
end
end
class Warrior
prepend Sword
def sword_name
'倚天剑'
end
end
warrior = Warrior.new
warrior.sword_name #=> "Silver Sword"
除覆盖以外,我们提到可以用关键字#super
来保留原本的用法。下列为使用 #super
的执行结果
module Sword
mattr_accessor :material, default: :silver
def sword_name
puts %Q(#========= #{[material.capitalize, 'Sword'].join(' ')} =========#)
super
end
end
class Warrior
prepend Sword
def sword_name
'倚天剑'
end
end
warrior = Warrior.new
warrior.sword_name
#== 先印: #========= Silver Sword =========#
#== 再回传: => "Silver Sword"
这种方式很像是controller
常使用的 before_action
行为,反之如果我们改成以下写法,就类似after_action
。
module Sword
mattr_accessor :material, default: :silver
def sword_name
super
puts %Q(#========= #{[material.capitalize, 'Sword'].join(' ')} =========#)
end
end
当我们使用extend
,就可以继承类别方法。
module Sword
mattr_accessor :material, default: :silver
def sword_name
[material.capitalize, 'Sword'].join(' ')
end
end
class Warrior
extend Sword
end
Warrior.sword_name #=> "Silver Sword"
我们可以透过 #included
的方法,一并使用类别方法和实体方法。
module Notification::Helper
# 实体方法写在这里
def self.included(base)
puts "MyModule is included to #{base}"
base.extend(ClassMethods)
# 等同於 def foo; 'foo' end
define_method :foo, -> {'foo'}
#=> add instance method
subclass.class_eval do
end
#=> add class method
subclass.instance_eval do
end
end
# 类别方法写在这里
module ClassMethods
def call(params = {})
new(params).call
end
# 等同於 def bar; 'bar' end
define_method :bar, -> {'bar'}
end
def call
{
title: @title,
content: @content
}
end
end
class Noti
include Notification::Helper
def initialize(**argv)
@title, @content = argv[:title], argv[:content]
end
end
#== # MyModule is included to Noti
Noti.bar #=> bar
Noti.new.foo #=> foo
Noti.call(title: '我是标题', content: '我是内文') #=> {:title=>"我是标题", :content=>"我是内文"}
包括#included
,一共有以下这些hook
可以使用。我们今天讲了前三个,明天会讲最後一个
Module#included
Module#extended
Module#prepended
Class#inherited
若想要看截至为止的继承链,我们可以使用ancestors
。
Noti.ancestors
#=> [Noti, Notification::Helper, Object, Kernel, BasicObject]
ancestors
意思为祖先,如果有看幽游白书的读者们,可以知道雷禅是幽助的16代前的祖先。长相如下 ⬇️
幽助.ancestors
#=> [幽助, ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., 雷禅]
在继承链里面的方法撞名时,ancestors
可以帮助我们判断,最後是哪个方法覆写了其他同名的方法。假设Noti
, Notification::Helper
都有方法foo
,则Noti
会覆盖Notification::Helper
的foo
module A
end
module B
end
class Wanc
include A
include B
end
class Gang
prepend A
prepend B
end
Wanc.ancestors #=> [Wanc, B, A, Object, Kernel, BasicObject]
Gang.ancestors #=> [B, A, Gang, Object, Kernel, BasicObject]
搭配include
, prepend
的用法搭配ancestors
查看,可以看到先後顺序不一样。
以下总结class
, module
之间用法的差别。
Class | Module | |
---|---|---|
可初始化 | 有 | 无 |
用法 | 创建物件 | namespace & mixin 继承 |
继承 | 可以 | 不行 |
可用的hook | #inherited | #included, #extended, #prepended |
明天开始讲 Ruby 的继承!
<<: Day 01-AWS Solution Architect Associate的铁人之旅行前会
>>: Day 14 Azure cognitive service: Text-to-Speech- Azure 念给你听
试想当你有了一个绝佳的 idea,如何验证这个概念可不可行、够不够好呢?设计师常用的方法,是制做一个...
“There is an infinite amount of hope in the unive...
本文将於赛後同步刊登於笔者部落格 有兴趣学习更多 Kubernetes/DevOps/Linux 相...
上一次介绍完了介面,今天就要来说说实作的部分了,从这里开始我要采取一种“小步快跑”的方式,原本 Ed...
在 Ruby 内有符号(Symbol)这个物件,他跟字串的用法蛮像的,但本质上则不一样。 究竟 Sy...