类与实例
2016年9月5日 下午7:35 ***
一个类里面会定义一些方法,类存在的理由就是要被实例化,也就是去创建一个类的实例得到一个对象。一个实例化的动作,像这样:
obj = Object.new
Object 是 Ruby 内置的一个类,在类上使用点形式,就是 Object 与 new 之间的那个点。你就是发送了一个信息给类。类会对这个信息做出响应,就像对象可以响应信息一样。类也是对象。new 方法是一个构造器,也就是类里面的可以加工与返回新实例的方法。
使用 class 关键词可以去定义类,类的名字用的是常量,使用大写字母开头。常量可以存储信息,常量的值是可以改变的,但是你不能为常量去分配一个新的值,这样 Ruby 会警告你。最好的方法是避免为已经有值的常量分配新的值。
来创建一个 Ticket 类,它里面有一个简单的方法:
class Ticket def event 'can not really be specified yet...' end end
现在可以创建一个新的 ticket 对象,然后问一下它的 event:
ticket = Ticket.new puts ticket.event
调用方法 ticket.event ,会执行 event 方法,也就会输出这个方法里要输出的东西:
can not really be specified yet...
在上面的例子里我们创建与执行了一个实例方法。
下午7:47 ****
实例方法
之前我们直接在一个对象的上面定义了一个方法,是这样做的:
def ticket.event
刚才我们在 Ticket 类里又定义一下 event 这个方法:
def event
这种在类里定义的方法会出现在所有的这个类的实例上,这种方法就是实例方法(instance methods)。它们不会只属于一个对象,所有的实例都可以调用它们。
我们在某个特定的对象上面定义的方法叫做独立方法(singleton methods),比如像 def ticket.price 。一个对象有一个 price 方法,这个对象不会在乎这个方法到底是一个独立方法还是一个实例方法,不过作为程序员我们应该知道它们的区别。
下午7:54 ****
覆盖方法
下午7:54 ****
在类里面定义了方法,我们可以重新再定义它,在下面的例子里重复定义了两次 m 这个方法:
class C def m puts '第一次定义方法 m' end def m puts '第二次定义方法 m' end end
来看一下我们在 C 的一个实例上调用 m 这个方法会发生什么:
C.new.m
输出的结果是 “第二次定义方法 m ”,第二次的定义胜利了,因为我们看到的是第二次定义 m 这个方法的时候要输出的东西。当我们覆盖一个方法的时候,新的版本会胜利。
下午8:02 ***
重新打开类
下午8:02 ***
大部分情况下,定义一个类,你就创建了一个类定义的区块:
class C # 这里是类的代码 end
重新再打开这个类添加额外的东西或者修改是可以做到的。像这样:
class C def x end end class C def y end end
我们打开了类的定义主体,添加了一个 x 方法,关掉了定义主体。然后,我们又重新打开了定义主体,添加了第二个方法 y ,又关掉了定义主体。跟下面这么做是一样的效果:
class C def x end def y end end
下午8:07 ****
实例变量与对象状态
下午8:09 ****
跟一个对象相关的信息与数据是这个对象的状态(state)。我们需要做到:
- 设置与重置一个对象的状态(一张票说,你花了 10 块钱)
- 读回状态(问一张票,你需要花多少钱?)
实例变量(instance variables),就是 Ruby 对象使用的存储与取回值的机制。实例变量让每个独立的对象可以记住它们的状态。实例变量跟其它的变量差不多,你分配值给它们,你可以读回这些值,你可以把它们加到一块儿,输出它们等等。不过有几个不一样的地方:
- 实例变量的名字要用 @ 符号开头,这样你很容易知道哪些是实例变量。
- 实例变量只能在它们所属的那个对象的内部使用。
- 在类的内部的某个方法里面初始化一个实例变量,这个实例变量就可以在类的任何方法里面使用。
来看个例子:
class Person def set_name(string) puts '设置人名...' @name = string end def get_name puts '返回人名...' @name end end joe = Person.new joe.set_name('Joe') puts joe.get_name
调用 set_name 方法的时候我们在这个方法里分配了一个实例变量,名字是 @name,这个变量也可以在类里的其它的方法上使用,比如我们在 get_name 这个方法里就用了一个 @name 这个实例变量。
下午8:25 ****
初始化带状态的对象
下午8:27 ****
在类里面可以定义一个名字是 initialize 的方法,每次创建新的实例的时候,都会自动调用这个方法。来看个例子:
class Ticket def initialize puts '创建新票' end end
调用 Ticket.new 的时候,你就会看到一个 “创建新票” 的信息出现。
你可以利用这个自动初始化的方法,在创建对象的时候去设置对象里的状态。比如你想在创建票的时候让票拥有会场(venue)与日期(date)这两个状态,你可以在创建实例的时候让它们作为参数的值,这些参数的值也会传递给 initialize 方法,这样你就可以把这些参数的值保存成实例变量:
class Ticket def initialize(venue,date) @venue = venue @date = date end
关闭定义类的区块之前,再做点别的事,就是添加一种可以读回 venue 与 date 值的方法。继续再添加下面这段代码:
def venue @venue end def date @date end end
这两个方法里面用到了实例变量,而且它们都是方法里的最后一个表达式,也就是它们会作为方法返回的值。
下午8:40 ****
设置器方法
下午8:51 ***
名字里带等号的方法
class Ticket def initialize(venue,date,price) @venue = venue @date = date @price = price end def price @price end end
初始化的那个方法会变得很长,而且我们还得记住参数的顺序。可以改进一下,比如我们用一个 set_price 方法,可以用来设置或重置票的价格,这样再改一下:
class Ticket def initialize(venue,date,price) @venue = venue @date = date end def set_price(amount) @price = amount end def price @price end end
上面的 set_price 也可以写成这样:
def price=(amount) @price = amount end
方法是用 = 号结尾的,使用它的时候可以这样:
ticket.price=(63.00)
或者直接这样用:
ticket.price = 63.00
上面其实是一个方法的调用,不过它看起来像是一个分配的表达式。这种形式是 Syntactic sugar,它表示的是一种使用特别规则的写法。
下午9:32 ****
属性与 attr* 方法家族
2016年9月6日 上午7:38 ***
attribute 与 property 翻译成中文都可以是 “属性”。属性的值可以通过对象读与写。比如之前我们见过的那个 票 对象,每张票都有 price,date,venue 属性(attribute)。price= 这个方法可以看成是属性的写的方法,date,venue,还有 price 这些读取属性用的方法。write/read 跟 get/set 是一个意思。
自动创建属性
再看一下之前的这段代码:
class Ticket def initialize(venue, date) @venue = venue @date = date end def price=(price) @price = price end def venue @venue end def date @date end def price @price end end
从属性的读写方面来看,上面有一个读/写属性(price),还有两个读属性(venue 和 date)。像上面这么干也可以,不过就是有点重复,比如这三个方法的形式都是这样的:
def something @something end
Ruby 提供了自动创建读取与返回实例变量值的方法,像这样:
class Ticket attr_reader :venue, :date, :price end
上面有几个东西是用冒号开头的,这些是符号(symbols),符号是命名或打标记用的东西。以后再具体解释它跟字符串的区别。
self 是默认的接受者
你看到了一些调用的方法没有明显的接受者,比如调用 attr_reader 的时候。在这种情况下,信息会发送给 self ,它是默认的对象。在定义类的主体的顶级,self 是类对象本身,也就是接受 attr_reader 信息的其实是 Ticket 这个类对象。
还有个 attr_writer 方法,像这样用:
class Ticket attr_writer :price end
再用这两个 attr_* 方法改进一下 Ticket 类:
class Ticket attr_reader :venue, :date, :price attr_writer :price def initialize(venue, date) @venue = venue @date = date end end
这样一个 ticket 对象会有 venue,date,price 这几个属性,venue,date 是可读属性,price 是可读也可写的属性。
用 attr_accessor 创建读写属性
使用 attr_accessor 可以为属性创建读与写的方法。它相当于是 attr_reader 加上 attr_writer 。
class Ticket attr_reader :venue, :date attr_accessor :price # ... etc. end
还有个 attr ,这样用:
attr :price, true
第二个参数 true 表示使用读与写的方法。
attr* 方法的总结
attr_reader
代码: attr_reader :price 相当于: def price @price end
attr_writer
代码: attr_writer :price 相当于: def price=(price) @price = price end
attr_accessor
代码: attr_accessor :price 相当于: def price=(price) @price = price end def price @price end
attr
代码: # 相当于 attr_reader attr :price # 相当于 attr_accessor attr :price, true
上午8:26 ***
继承
上午9:00 ***
继承是两个类之间的一种关系,一个类从另一个类里继承了一些行为(subclass,superclass)。看一个例子,Magazine 继承了 Publication ,Magazine 是 Publication 类的 subclass,Publication 是 Magazine 类的 superclass:
class Publication attr_accessor :publisher end class Magazine < Publication attr_accessor :editor end
Magzine 继承的时候用了一个 < 符号,它是一个 subclass,它继承的是 Publication 这个类,这个类对于 Magzine 这个类来说是一个 superclass。
做个实验:
mag = Magazine.new mag.publisher = 'ninghao.net' mag.editor = '张三' puts '#{mag.publisher} 是杂志的发行者,#{mag.editor} 是杂志的编辑'
因为 Magzine 类继承了 Publication 类,所以 Magzine 的对象里面也会包含 Publication 里面的属性与方法。
我们可以继续:
class Ezine < Magzine end
这样 Ezine 的对象里面就会有 publisher 和 editor 这两个属性。每个 Ruby 类只能继承一个类。
做个实验:
class C end class D < C end puts D.superclass puts D.superclass.superclass
输出的是:
C Object
C 是 D 的 superclass,Object 是 C 的 superclass 。
上午9:12 ***
作为对象与信息接受者的类
上午9:12 ***
类是一种特别的对象,它是唯一的能生对象的对象。创建了一个类,比如 Ticket ,你可以发送信息给它,添加方法给它,把它作为方法的参数传递给其它的对象,你可以像处理其它的对象一样处理类对象。
创建类对象
所有的者,比如 Object,Person,Ticket 都是 Class 这个类的实例。使用 class 关键词创建一个类对象:
class Ticket # 代码 end
使用上面这种方法创建类对象非常容易读懂,你也可以这样来创建类对象:
my_class = Class.new
上面创建的这个类对象可以创建它自己的实例:
instance_of_my_class = my_class.new
如果你想使用 Class.new 去创建一个匿名的类,而且想在创建它的时候给它添加实例方法,可以在 Class.new 的后面添加一个代码块(code block),像这样:
c = Class.new do def say_hello puts 'hello' end end
上午10:17 ****
Ruby 对象的天性与培育
is_a? 方法可以告诉你一个实例是否属于某个类,或者它所属类的祖先类:
>> mag = Magazine.new => # >> mag.is_a?(Magazine) => true >> mag.is_a?(Publication) => true
一个实例不完全受它所属的类的影响,你可以为单独的实例对象添加方法,比如让 magzine 长出翅膀:
mag = Magazine.new def mag.wings puts 'I can fly!' end mag.wings
常量
上午10:17 ****
常量的名字是大写字母开头的。类的名字是常量,常量也经常会用在类里面来保存重要的数据。
一个例子:
class Ticket VENUES = ["Convention Center", "Fairgrounds", "Town Hall"]
如果你想让每张票都来自 VENUES 里定义的场地,可以在类的 initialize 方法里这样做:
def initialize(venue, date) if VENUES.include?(venue) @venue = venue else raise ArgumentError, "Unknown venue #{venue}" end @date = date end
在定义类的外面可以使用双冒号得到常量的值:
class Ticket VENUES = ["Convention Center", "Fairgrounds", "Town Hall"] end puts Ticket::VENUES
Ruby 的预定义常量
>> Math::PI => 3.141592653589793 >> RUBY_VERSION => "2.3.1" >> RUBY_PATCHLEVEL => 112 >> RUBY_RELEASE_DATE => "2016-04-26" >> RUBY_REVISION => 54768 >> RUBY_COPYRIGHT => "ruby - Copyright (C) 1993-2016 Yukihiro Matsumoto" >>
重新分配与修改常量
给一个已经分配了值的常量重新分配值,会收到 Ruby 的警告,试一下:
>> A = 1 => 1 >> A = 2 (irb):36: warning: already initialized constant A (irb):35: warning: previous definition of A was here => 2
venues = Ticket:VENUES venues << '体育馆'