前情提要:面试
第12天,往细节探索去!昨天咱们讲到broc是有名字的内存块物件,可储存变数;lambda是一种method方法,严格检查参数数目。今天想要更深地讨论变数:)对象
Ruby经典面试题目#12继承
Ruby的类别变数与类别实体变数,与实体变数有何不一样?What is difference between class variable,class instance variable and instance variable?内存
咱们曾在第四天时讨论过类别方法和实体方法(leafor)。get
还记得我下的这个结论:it
若是要将实体方法,运用在某个客制化的实体,就使用instance method;class
若是某个方法并不会和某个特定的实体变数绑在一块儿,就使用class method。lambda
实体变数instance variablesso
实体变数是一个比较好理解的概念,来举个例子吧:程序
我想把天天跑步的习惯RunDaily写成class,为了持续维持好习惯,方法有两个:早上跑morning_run或者晚上跑evening_run,若是想早上跑(morning run),实体变数@mr会带入参数5km,晚上(evening)跑的话,@ er带入10km。
今天是第12天了~
咱们创造出day12的以下:
class RunDaily
def morning_run(km)
@mr = km
end
def evening_run(km)
@er = km
end
end
day12 = RunDaily.new
p day12.morning_run(5)
p day12
p day12.evening_run(10)
p day12
咱们能够看到实体变数(instance variable)以@开头,不须要先在class开头宣告,缘由是:
Ruby的实体变数不是public,仅做用于于self指示的物件。除非明确提供其余方法,
不然没法从物件之外变动或查看。原文
5
#<RunDaily:0x000055e64755a770 @mr=5>
10
#<RunDaily:0x000055e64755a770 @mr=5,@er=10>
从输出结果看到day12这个物件的方法是Rundaily,动态地加入了两个实体变数mr和er。
实体变数的属性(attribute)
物件的实体变数,就是物件的属性(attribute),就算是同一个class的不一样物件,其属性也不一样。
(还记得咱们在Ruby铁人赛第一天提到,Ruby世界观里,万物皆物件吗?)
我很喜欢一种说法:每一天都是全新的一天,昨天、今天,明天都是不一样的物件,独立的个体:)
让咱们来创造新物件,解释不一样物件的属性。
假设我创造了明天这个新物件(第13天)Day13遇到休假日,因此早上一口气跑了21km:
day13 = RunDaily.new
p day13.morning_run(21)
p day13
结果显示此物件存在于不一样的內存位置,并且变数也不一样:
21
#<RunDaily:0x0000561a9376e1d0 @mr=21>
属性存取器(attribute accessors)
就像咱们有时候会想知道每一个特定的日子分别跑了几千米,或者是从新提取天天铁人赛的文章内容究竟是什么。
这时候可以读取实体变数的属性是很是重要的,让咱们能够更方便的读取这些不一样的物件(由于,凡走过必留下痕迹!就像翻开本身写过的日记或铁人赛同样。)
案例一:Yesterday
如今来IronmanDairy类别里写一个属性存取器(attribute accessors)的公开方法,让咱们能够设定(set_dairy)、取得(get_dairy)昨天Day12的铁人赛文章标题:
class IronmanDairy
def set_dairy(title)#write dairy
@title = title
end
def get_dairy #read dairy
@title
end
end
day11 = IronmanDairy.new
p day11
day11.set_dairy(“Explain the difference between block,proc,lamdba.”)
day11.get_dairy #取出昨天文章的标题
p day11
日记day11物件被咱们读取出来了:显示出內存位置,及@title实体变数:
#<IronmanDairy:0x000055d4f44e2748>
#<IronmanDairy:0x000055d4f44e2748 @title=“Explain the difference between block,proc,lamdba.”>
案例二:Today
set_dairy和get_dairy方法虽然让咱们易于了解属性的写入与读取方式,但把细节拆解开来的代码却显得过于冗长。
为了产生同时具备读Read+写Write功能的实体变数(在这里是@title),每次都要写出这一对set_dairy和get_dairy方法,不是很累吗?
(就像写日记同样,你不会把写日记解释为:这是一组打开日记本,写日记,而后再阖上日记本的动做。)
你就只是想。要。写。日。记而已!
有没有精简的方法呢?
(你猜对了!只要仔细找一找手册,Ruby里一般都有方法!)
为了秉持着每个今天都比昨天更好的精神,咱们提出改良版本:
假设咱们要写第12天新文章day12,能够利用写入title=method,及取得titlemethod,查看文章标题,取代本来的set_dairy和get_dairy:
class IronmanDairy
def title=(title)#write dairy
@title = title
end
def title #read dairy
@title
end
end
day12 = IronmanDairy.new
day12.title =“class variable,class instance variable and instance variable”
p day12
p day12.title
结果印出:
#<IronmanDairy:0x00005648020c0828>
#<IronmanDairy:0x00005648020c0828 @title=“class variable,class instance variable and instance variable”>
注意,这里的title=也是一个实体方法,咱们来用.instance_methods确认一下:
p IronmanDairy.instance_methods(false)#=> [:title=,:title]
案例三:Tomorrow
有没有发现上面的代码中,大量出现这个@title实体变数呢?若是想要更进一步简化,能够用attr_accessor方式改写。
假设咱们要创一个明天Day13铁人赛文章物件,直接把实体的属性存取器attr_accessor:title,指定给symbol:title,加在类别的开头便可:
class IronmanDairy
attr_accessor:title
end
day13 = IronmanDairy.new
p day13 #<IronmanDairy:0x00005579aee8bc00>
day13.title =“Still thinking…”
p day13 #<IronmanDairy:0x00005579aee8bc00 @title=“Still thinking…”>
p day13.title #“Still thinking…”
p IronmanDairy.instance_methods(false)#[:title=,:title]
从以上的[Yesterday,Today,Tomorrow]三个举例,表明持续改良提取物件属性的写法,是否是可以对于实体变数有全方位的了解了呀?
类别变数class variable
类别变数在类别开头,用@@定义,它是个危险的东西,由于全部的子类别中对类别变数的改动,都会影响其余类别的变数。咱们用「鸡兔同笼」的例子,来算算不一样的动物各有几只脚:
class Animal
@@legs = nil #先预设动物的脚为空值nil
def self.legs
@@legs
end
end
p Animal.legs # => nil
class Chicken < Animal #`鸡`类别继承`动物`类别
@@legs = 2
end
p Chicken.legs # => 2
p Animal.legs # => 2动物变2只脚了!
class Rabbit < Animal
@@legs = 4
end
p Rabbit.legs # => 4
p Animal.legs # => 4,动物又变4只脚了!
class Snake < Animal #笼子里加入一只蛇
@@legs = 0 #蛇没有脚!
end
p Animal.legs # => 0
p Snake.legs # => 0
p Rabbit.legs # => 0糟糕,为何此次兔子没有脚!~~被蛇吃掉了~~
为了解决此问题,咱们来研究Ruby的类别实体变数,看看是否有更好的作法。
类别实体变数class instance variable
咱们在Day1中开宗明义地解释面向对象语言的精髓:物件能够具备类别和实体变数。既然类别也是一种物件,那「类别物件」固然能够有「类别的实体变数」。咱们继续「蛇兔同笼」的例子,举例出三种变数大乱斗:
class Animal #案例1: animal类别- class variable
@@legs = nil #设定类别变数为nil
def self.legs
@@legs
end
end
p Animal.class_variables #印出类别变数:@@legs
p Animal.legs #类别变数:目前为空值nil
p Animal.instance_variables # => []还没有设定实体变数,因此没东西
class Animal #案例2: animal类别- instance variable
attr_accessor:legs #设定实体变数
@legs = 0
end
p Animal.instance_variables # =>如今能印出实体变数:@legs
p Animal.legs #仍然是类别变数的空值nil(dcjwsc.com)
class Animal #案例3: animal类别- class instance variable
class << self;attr_accessor:legs end
#self在类别class里,表明目前所在之处的animal类别(而不是案例1和案例2的同名类别唷)
@legs = 1 #设定「类别实体变数」,先预设为1
end
p Animal.legs # => 1 #不是nil,不是0,而是1(类别实体变数!)
class Rabbit < Animal
@legs = 4
end
p Rabbit.legs # =>兔子4只脚
p Animal.legs # =>回到类别实体变数预设值1
class Snake < Animal
@legs = 0
end
p Snake.legs # =>蛇0只脚
p Rabbit.legs # =>兔子仍是4只脚!太好了~没有被吃掉
p Animal.legs # =>回到类别实体变数预设值1
以上的举例实实在在地证实我在这本书Effective Ruby中文版-写出良好Ruby程序的48个具体作法Page 56查到的观点:宁愿用类别实体变数,也不要用类别变数。类别实体变数除了会打破类别及其子类别的共享关系(如同咱们举的例子中,动物的脚数目随意被改变),也提供更多的封装,让类别定义层级、或从类别方法里,惟一可存取的是类别实体变数。
最后用比一比的表格来总结:)
类别变数class variable类别实体变数class instance variable实体变数instance variable
@@类别变数@类别实体变数@实体变数
在类别开头设定可用attr_accessor的方式改写可用attr_accessor的方式改写
可用在类别方法或实体方法用在类别方法,不可用在实体方法用在实体方法