Day12 -千变万化的变数:class variable,class instance variable与instance variable

前情提要:

第12天,往细节探索去!昨天我们讲到broc是有名字的内存块物件,可储存变数;lambda是一种method方法,严格检查参数数目。今天想要更深地讨论变数:)

Ruby经典面试题目#12

Ruby的类别变数与类别实体变数,与实体变数有何不同?What is difference between class variable,class instance variable and instance variable?

我们曾在第四天时讨论过类别方法和实体方法(leafor)。

还记得我下的这个结论:

如果要将实体方法,运用在某个客制化的实体,就使用instance method;

如果某个方法并不会和某个特定的实体变数绑在一起,就使用class method。

实体变数instance variable

实体变数是一个比较好理解的概念,来举个例子吧:

我想把每天跑步的习惯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的方式改写

可用在类别方法或实体方法用在类别方法,不可用在实体方法用在实体方法

猜你喜欢

转载自www.cnblogs.com/lannyQ-Q/p/11095418.html
今日推荐