Class 是Ruby
很重要的观念,要学习 Ruby
的一定要学会class & 物件。我们会在Day11-16 详细讲解何谓 class
。
以下为Day11
会提及的内容
除了无法单独存在的Block
以外,所有的东西都是物件。
写Ruby and Rails
一段时间後发现,在Rails的专案中,我们只能在定义models
, controllers
的class
里面定义行为,不过大家有没有想过,到底是哪个执行绪跑了这些class
? Ruby on Rails
替我们做了很多事情,只要我们遵循惯例走就好。
写一段时间Rails
的朋友们,可能已经习以为常,但仔细想在专案里面,下列的写法有很多看点
Controller
不用写建构子?<
在 Rails
为继承,那被继承的 ApplicationController
, ApplicationRecord
又是什麽?before_action
是如何被实作?# Controller: controllers/admin/order_controller.rb
module Admin
class OrdersController < ApplicationController
end
end
# Model: models/order.rb
class Order < ApplicationRecord
end
这系列的文章可能没有办法带到太多Rails
的观念,不过汉汉老师会不断的在文章里面提到自己使用Rails
的一些经验
建构子就是在初始化的时候给定一些变数,以及基本的设定。constructor
,也有人叫做initializer
,所以只要看到这两个英文单字就是在讲建构子。以Ruby
程序语言来说的话,我们会在建构子初始化设定值在实体变数instance variable
。
以下方的车子为例,我们可以透过建构子指定车子的颜色、车子的大小。在 Ruby
中,建构子以initialize
表示。
class Car
def initialize(color, size)
@color, @size = color, size
end
end
red_car = Car.new('red', 'big') #=> #<Car:0x00007fd4ddc8ffa0 @color="red", @size="big">
blue_car = Car.new('blue', 'big') #=> #<Car:0x00007fd4ddc8c3c8 @color="blue", @size="big">
我们可以想像到,当我们初始化一个物件red_car
, blue_car
,Car
就像是工厂,red_car
, blue_car
y则是被产出来的物件,而工厂就是Car
类别,然而读者也要清楚知道,Car
也是被创造的物件。在 Ruby 语言当中,所有物件的源头都在Basic Object
。
介绍一个方法superclass
,透过superclass
我们可以找到 Ruby 物件的源头
Car.superclass
#=> Object
Car.superclass.superclass
#=> BasicObject
Car.superclass.superclass.superclass
#=> nil
⭐️我们可以在建构子做计算
class DataSet
def initialize(ary)
@ary = ary
@size = ary.is_a?(Array) ? ary.count : 0
end
end
data_it = DataSet.new([1, 2, 3]) #=> #<DataSet:0x00007fd4ddcfd0f0 @ary=[1, 2, 3], @size=3>
我们可以在建构子设定参数的预设值。参数的预设值除了可以使用 ||
来表示以外,还可以在括号里面填入方法名後面的参数填入预设值
#=> default value with || operator
class Animal
def initialize(*args)
@species, @examples = args[0] || '猩猩', args[1] || '小庞'
end
end
orangutan = Animal.new #=> #<Animal:0x00007fd4ddd2d980 @species="猩猩", @examples="小庞">
#=> default value with assigning arguments
class Animal
def initialize(species = '猩猩', examples = '小庞')
@species, @examples = species, examples
end
end
orangutan = Animal.new #=> #<Animal:0x00007fd4ddd56d58 @species="猩猩", @examples="小庞">
另外我们也说明,括号在Ruby里面可有可无,举例来说,下列两种宣告新物件的方法相同,不过依照惯例,若没有必要使用初始值,我们不用写括号。
orangutan = Animal.new
orangutan = Animal.new()
⭐️ 在一般的程序语言中,预设参数的给法若在顺序上有差异,使用上也会有差别。
class Animal
def initialize(species, examples = '小庞')
@species, @examples = species, examples
end
end
turtle = Animal.new('甲鱼') #=> #<Animal:0x00007fd4ddd763b0 @species="甲鱼", @examples="小庞">
⭐️ 不过Ruby
在某些状况下会自己判断预设值
class Animal
def initialize(species = '猩猩', examples)
@species, @examples = species, examples
end
end
orangutan = Animal.new('阿豪') #=> #<Animal:0x00007fd4dddb6550 @species="猩猩", @examples="阿豪">
⭐️ 宣告新物件时,括号可以省略
orangutan = Animal.new('阿豪')
orangutan = Animal.new '阿豪'
⭐️ 有些特殊物件的宣告是可以不用透过new
,像阵列、字串等。我们会比较偏爱literals constructor
的用法胜於使用一般建构子的宣告方式
# 这样用会比较冗长,比较少人会这样用
ary = Array.new
str = String.new
# literal constructor
ary = [1, 2, 3]
str = "qwer"
⭐️ 看一下如何使用 hash 做为参数宣告
class Animal
def initialize(species = '猩猩', examples:)
@species, @examples = species, examples
end
end
orangutan = Animal.new(examples: '阿豪') #=> #<Animal:0x00007fd4de0530d8 @species="猩猩", @examples="阿豪">
⭐️ Hash 搭配预设值
class Animal
def initialize(species = '猩猩', examples: '小庞')
@species, @examples = species, examples
end
end
orangutan = Animal.new
⭐️ 使用 options = {}
搭配建构子宣告
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小庞'
end
end
orangutan = Animal.new #=> #<Animal:0x00007fd4d3d1b8c0 @species="猩猩", @examples="小庞">
⭐️ 以下写法会造成语法错误
# 语法错误 Predefined Args cannot allowed after named arg
class Animal
def initialize(species = '猩猩', examples: '小庞', options = {})
@species, @examples = species, examples
end
end
⭐️ 预设值搭配Hash
的使用方法要注意
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小庞'
end
end
#=> {:a=>"a"} 被当作 @species
#=> #<Animal:0x00007fd4d794d028 @species={:a=>"a"}, @examples="小庞">
orangutan = Animal.new(a: 'a')
# 预期内的行为
#=> #<Animal:0x00007fd4d9c8eb18 @species="猩猩", @examples="小庞">
orangutan = Animal.new('猩猩', a: 'a')
hash
预设值填在options
里面,而若宣告新物件给a: 'a'
,原本的examples: '小庞'
将会被覆盖。
class Animal
def initialize(species = '猩猩', options = {examples: '小庞'})
@species, @examples = species, options[:examples]
end
end
#=> #<Animal:0x00007fd4d9f67e70 @species="猩猩", @examples=nil>
orangutan = Animal.new('猩猩', a: 'a')
我们也可以使用splat operator
class Animal
def initialize(species = '猩猩', **options)
@species, @examples = species, options[:examples] || '小庞'
end
end
#=> #<Animal:0x00007fd4dd1195b8 @species="猩猩", @examples="小庞">
orangutan = Animal.new('猩猩', a: 'a')
block
可以当作变数使用
class Animal
def initialize(species = '猩猩', it_day11)
@species, @block = species, it_day11
end
end
orangutan = Animal.new('猩猩', -> (x) {x + 1})
#=> #<Animal:0x00007fd4db77f558 @species="猩猩", @block=#<Proc:0x00007fd4db77f5a8@(irb):164 (lambda)>>
在Ruby
的世界中,我们可以使用getter
, setter
取得与改写值。当我们想要取得动物的种类找不到,是因为我们没有给方法取得值。
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小庞'
end
end
orangutan = Animal.new
orangutan.species # NoMethodError (undefined method `species' for #<Animal:0x00007fd4dcad19a8>)
⭐️ 我们可以透过一些方法取得值
# 法1. 透过 instance_eval 取得值
orangutan.instance_eval { @species } #=> 猩猩
orangutan.instance_eval { species } #=> 猩猩
# 法2. 对 orangutan 加入 getter 的单体方法,使其可以取用值
class << orangutan
def species
@species
end
end
# 可以使用 getter
orangutan.species #=> 猩猩
⭐️ 接着开始介绍getter
,以下为 getter
的使用方式
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小庞'
end
def species
@species
end
end
orangutan = Animal.new
orangutan.species #=> 猩猩
⭐️Ruby
的getter
,有更简洁的写法
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小庞'
end
attr_reader :species
end
orangutan = Animal.new
orangutan.species #=> 猩猩
⭐️ 我们可以利用setter
来去设定实体变数
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小庞'
end
# getter
attr_reader :species
# setter
def species=(species)
@species = species
end
end
orangutan = Animal.new
orangutan.species = '猩'
orangutan.species #=> 猩
⭐️Ruby
也有更简洁的用法
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小庞'
end
# getter
attr_reader :species
# setter
attr_writer :species
end
orangutan = Animal.new
orangutan.species = '猩'
orangutan.species #=> 猩
⭐️ 对於取值、改写值的用法,Ruby
提供了 attr_accessor
作为getter
, setter
使用。
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小庞'
end
# getter/setter
attr_accessor :species
end
orangutan = Animal.new
orangutan.species = '猩'
orangutan.species #=> 猩
⭐️ 若要对getter, setter 进行客制化功能的话,只能够自己写
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小庞'
end
attr_reader :species
# setter
def species=(species)
@species = "❌ #{species}"
end
end
orangutan = Animal.new
orangutan.species = '猩'
orangutan.species #=> "❌ 猩"
下列的例子中,drive
为物件的方法,称为instance method
class Car
def initialize(color, size)
@color, @size = color, size
end
def drive
[color.capitalize, 'car', 'boo boo!'].join(' ')
end
private
attr_reader :color
end
red_car = Car.new('red', 'big')
blue_car = Car.new('blue', 'big')
red_car.drive #=> "Red car boo boo!"
blue_car.drive #=> "Blue car boo boo!"
首先要澄清两件事情:
若不写attr_reader
一样可以动作。
color
为self.color
的简写,这边我们改使用实体变数@color
也可以。个人习惯在唯读的变数加入attr_reader
,并且毋需在外部取得值的地方,例如上例的@color
,汉汉老师就会把它写在private
里面,避免外部可以取得值。
在Ruby
的世界中,一共有public
, private
, protected
三种方法,目前还没有用过protected
过,还没有碰过需要区分的情境。若好奇的参考 这篇 论述即可。
class Car
# public 方法
private
# 私有方法
protected
# 保护方法
end
由analyse_color
可以得知,self.变数
等於实体变数
。此外,object_id
可以用来判断物件实际的位址。
class Car
def initialize(color, size)
@color, @size = color, size
end
def reflect
self
end
def analyse_color
color.object_id == @color.object_id
end
end
red_car.reflect #=> #<Car:0x00007fd065a07520 @color="red", @size="big">
red_car.reflect == red_car #=> true
red_car.analyse_color
实体变数为长相@
开头的变数,都为实体变数instance variable
。上面已经提到了很多实体变数的观念,这边会再详述实体变数的概念。
@color, @size, @species, @examples
实体变数是内部存放资料的地方,我们可以利用实体变数来取值和给值,并且我们不一定要在初始化的时候宣告实体变数。下例即为令新的实体变数@age
,并给他get
,set
两种方法(复习一下attr_accessor
)
class Car
def initialize(color = 'red', size = 'big')
@color, @size = color, size
end
attr_accessor :age
end
car = Car.new
car.age = 20
car.age #=> 20
我们实际看 car
物件的实体变数
car.instance_variables
#=> [:@color, :@size, :@age]
这边花了很大的篇幅,介绍了class
的基本用法,後面的章节会介绍类别方法。
如今已经到了第11天,看着订阅数与观看数的微幅增长,确实比起跟着股票的涨跌而心情起舞,感受还要好。看到有人愿意花时间点开我的文章,我真的很开心。
第一天列出了这一期铁人赛的大纲,在写的过程中一直改变想法,30篇的文章架构不断的再改变。一来是下班时间并没有那麽多,加上若一篇文章讯息量太大,反而会造成反效果。所以这阵子不断的调整文章,调整能够让读者看到更精辟的重点!
我也推荐同样是工程师的朋友们写文章,虽然表面上好像很花时间,比起自己在边工作的时候,只记自己看得懂的笔记。多用心记录,整理成主题式的文章,未来再反刍自己的文章也比较不会看不懂,其实算是很好的投资。
>>: [烧烤吃到饱-3] 猪对有韩式烤肉吃到饱-台中精武店 #中秋节烤肉精选店家
前言 写CSS还蛮常会用到阴影,使元素更具有立体感,今天就来认识一下阴影有什麽属性吧。 首先我们要先...
Hi 我是Fanny 接下来的日子要来分享有关这个营队点点滴滴, (排版不好敬请见谅) 首先今天一开...
在进到 " Shading " 之前,我们必须先调整 " Partic...
文件 原文文件:Page migration 翻译: .. _page_migration: ===...
前言: 大家好,这是我第一次参加铁人赛 主要是想记录一下自己学过的东西 并和大家分享一些我觉得很重要...