🦄 2024 独立开发者训练营,一起创业!查看介绍 / 立即报名(剩余10个优惠名额) →

Day 3:Ruby 组织对象用的类

类与实例

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 是可读也可写的属性。

941E615E-5ECF-4567-8217-DDD888F33DE5

用 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 << '体育馆'
上午10:49 ***
Ruby
微信好友

用微信扫描二维码,
加我好友。

微信公众号

用微信扫描二维码,
订阅宁皓网公众号。

240746680

用 QQ 扫描二维码,
加入宁皓网 QQ 群。

统计

15260
分钟
0
你学会了
0%
完成

社会化网络

关于

微信订阅号

扫描微信二维码关注宁皓网,每天进步一点