Ruby 简明教程

Ruby - Object Oriented

Ruby 是一种纯面向对象语言,在 Ruby 中一切都被视为对象。Ruby 中的每个值都是一个对象,即使是最原始的东西:字符串、数字甚至是 true 和 false。即使类本身也是 Object 类的一个实例的对象。本章将带你了解与面向对象 Ruby 相关的全部主要功能。

类用于指定对象的格式,它将数据表示和用于操作数据的函数组合在一个整洁的包中。类中的数据和方法称为类的成员。

Ruby Class Definition

当你定义一个类时,你就定义了一个数据类型的蓝图。这实际上并不定义任何数据,但它确实定义了类名意味着什么,即类的对象将由什么组成,以及可以在这样的对象上执行什么操作。

类定义以关键字 class 开始,随后是 class name ,并用 end 分隔。例如,我们使用关键字 class 定义 Box 类,如下所示:

class Box
   code
end

名称必须以大写字母开头,并且根据惯例,包含多个单词的名称会将每个单词的首字母大写并连接在一起,并且没有分隔符(驼峰式命名法)。

Define Ruby Objects

一个类为对象提供蓝图,因此一个对象基本上是从一个类中创建的。我们使用 new 关键字声明类的对象。以下语句声明类的两个对象 −

box1 = Box.new
box2 = Box.new

The initialize Method

initialize method 是一个标准 Ruby 类方法,并且作用方式几乎与 constructor 在其他面向对象编程语言中的作用方式相同。如果你想要在创建对象的时候对一些类变量进行初始化,initialize 方法是很有用的。此方法可以接受一个参数列表,并且像任何其他 ruby 方法一样,它会以 def 关键字为前导,如下所示 −

class Box
   def initialize(w,h)
      @width, @height = w, h
   end
end

The instance Variables

instance variables 是一种类属性,一旦使用类创建了对象,它们就会成为对象的属性。每个对象的属性都单独分配,并且不与其他对象共享值。它们在类中使用 @ 运算符进行访问,但是要在类外部访问它们,我们使用 public 方法,它们称为 accessor methods 。如果我们采用上面定义的类 Box ,那么 @width 和 @height 就是类 Box 的实例变量。

class Box
   def initialize(w,h)
      # assign instance variables
      @width, @height = w, h
   end
end

The accessor & setter Methods

要让变量在类外部可用,它们必须在 accessor methods 内进行定义,这些访问器方法也称为 getter 方法。以下示例显示了访问器方法的用法 −

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end

   # accessor methods
   def printWidth
      @width
   end

   def printHeight
      @height
   end
end

# create an object
box = Box.new(10, 20)

# use accessor methods
x = box.printWidth()
y = box.printHeight()

puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"

执行上述代码后,将生成以下结果 −

Width of the box is : 10
Height of the box is : 20

与用于访问变量值的访问器方法类似,Ruby 提供了一种使用 setter methods 从类外部设置这些变量值的方法,如下定义 −

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end

   # accessor methods
   def getWidth
      @width
   end
   def getHeight
      @height
   end

   # setter methods
   def setWidth=(value)
      @width = value
   end
   def setHeight=(value)
      @height = value
   end
end

# create an object
box = Box.new(10, 20)

# use setter methods
box.setWidth = 30
box.setHeight = 50

# use accessor methods
x = box.getWidth()
y = box.getHeight()

puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"

执行上述代码后,将生成以下结果 −

Width of the box is : 30
Height of the box is : 50

The instance Methods

instance methods 的定义方式与我们使用 def 关键字定义任何其他方法的方式相同,并且它们只能使用类实例来使用,如下所示。它们的功能并不仅限于访问实例变量,还可以根据你的要求执行更多操作。

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end
   # instance method
   def getArea
      @width * @height
   end
end

# create an object
box = Box.new(10, 20)

# call instance methods
a = box.getArea()
puts "Area of the box is : #{a}"

执行上述代码后,将生成以下结果 −

Area of the box is : 200

The class Methods and Variables

class variables 是一个变量,它在类的所有实例中共享。换句话说,该变量只有一个实例,它由对象实例访问。类变量使用两个 @ 字符(@@)作为前缀。必须在类定义中初始化类变量,如下所示。

类方法使用 def self.methodname() 定义,它以 end 定界符结尾,并且会使用类名作为 classname.methodname 调用,如下例所示 −

#!/usr/bin/ruby -w

class Box
   # Initialize our class variables
   @@count = 0
   def initialize(w,h)
      # assign instance avriables
      @width, @height = w, h

      @@count += 1
   end

   def self.printCount()
      puts "Box count is : #@@count"
   end
end

# create two object
box1 = Box.new(10, 20)
box2 = Box.new(30, 100)

# call class method to print box count
Box.printCount()

执行上述代码后,将生成以下结果 −

Box count is : 2

The to_s Method

你定义的任何类都应具有 to_s 实例方法以返回对象的字符串表示形式。以下是一个简单的示例,用于表示 Box 对象的宽度和高度 −

#!/usr/bin/ruby -w

class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end
   # define to_s method
   def to_s
      "(w:#@width,h:#@height)"  # string formatting of the object.
   end
end

# create an object
box = Box.new(10, 20)

# to_s method will be called in reference of string automatically.
puts "String representation of box is : #{box}"

执行上述代码后,将生成以下结果 −

String representation of box is : (w:10,h:20)

Access Control

Ruby 在实例方法级别提供三级保护,它们可能是 public, private, or protected 。Ruby 对实例和类变量不适用任何访问控制。

  1. Public Methods − 任何人都可以调用公共方法。除了 initialize 外,方法都是公有的(initialize 始终是私有的)。

  2. Private Methods − 无法从类外部访问或查看私有方法。只有类方法可以访问私有成员。

  3. Protected Methods − 只有定义类及其子类对象才能调用受保护的方法。访问权限仅限于家族内部。

以下是一个简单的示例,展示了所有三个访问修饰符的语法 −

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end

   # instance method by default it is public
   def getArea
      getWidth() * getHeight
   end

   # define private accessor methods
   def getWidth
      @width
   end
   def getHeight
      @height
   end
   # make them private
   private :getWidth, :getHeight

   # instance method to print area
   def printArea
      @area = getWidth() * getHeight
      puts "Big box area is : #@area"
   end
   # make it protected
   protected :printArea
end

# create an object
box = Box.new(10, 20)

# call instance methods
a = box.getArea()
puts "Area of the box is : #{a}"

# try to call protected or methods
box.printArea()

当执行以上代码时,它会产生以下结果。在此,第一个方法成功调用,但第二个方法出现了问题。

Area of the box is : 200
test.rb:42: protected method `printArea' called for #
<Box:0xb7f11280 @height = 20, @width = 10> (NoMethodError)

Class Inheritance

面向对象编程中最重要的概念之一是继承。继承允许我们在另一个类的基础上定义一个类,这使创建和维护应用程序变得更加容易。

继承还提供了重用代码功能和快速实现时间的机会,但不幸的是,Ruby 不支持多层次继承,但 Ruby 支持 mixins 。混合就像多重继承的一种专门实现,其中仅继承接口部分。

在创建一个类时,程序员可以指定新类应该继承现有类的成员,而不是编写全新的数据成员和成员函数。这个现有的类被称为 base class or superclass ,新类被称为 derived class or sub-class

Ruby 也支持子类化的概念,即继承,以下示例解释了这个概念。扩展类的语法很简单。只需添加 < 字符和超类的名称到你的类语句中即可。例如,以下定义一个类 BigBox 作为 Box 的子类 −

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end
   # instance method
   def getArea
      @width * @height
   end
end

# define a subclass
class BigBox < Box

   # add a new instance method
   def printArea
      @area = @width * @height
      puts "Big box area is : #@area"
   end
end

# create an object
box = BigBox.new(10, 20)

# print the area
box.printArea()

执行上述代码后,将生成以下结果 −

Big box area is : 200

Methods Overriding

尽管可以在派生类中添加新功能,但有时您想要更改父类中已定义的方法的行为。您可以通过保持方法名称相同并覆盖方法的功能来做到这一点,如下例所示 -

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end
   # instance method
   def getArea
      @width * @height
   end
end

# define a subclass
class BigBox < Box

   # change existing getArea method as follows
   def getArea
      @area = @width * @height
      puts "Big box area is : #@area"
   end
end

# create an object
box = BigBox.new(10, 20)

# print the area using overriden method.
box.getArea()

Operator Overloading

我们希望 + 运算符使用 + 执行两个 Box 对象的向量加法,* 运算符根据一个标量乘以一个 Box 的宽度和高度,一元 - 运算符对 Box 的宽度和高度求反。下面是定义了数学运算符的 Box 类的版本 -

class Box
   def initialize(w,h)     # Initialize the width and height
      @width,@height = w, h
   end

   def +(other)       # Define + to do vector addition
      Box.new(@width + other.width, @height + other.height)
   end

   def -@           # Define unary minus to negate width and height
      Box.new(-@width, -@height)
   end

   def *(scalar)           # To perform scalar multiplication
      Box.new(@width*scalar, @height*scalar)
   end
end

Freezing Objects

有时,我们希望阻止对对象的更改。Object 中的 freeze 方法允许我们做到这一点,有效地将对象变成一个常量。可以通过调用 Object.freeze 来冻结任何对象。冻结的对象可能不会被修改:您不能更改其实例变量。

您可以使用 Object.frozen? 方法检查给定对象是否已冻结,该方法在对象被冻结的情况下返回 true,否则返回 false 值。以下示例阐明了概念 -

#!/usr/bin/ruby -w

# define a class
class Box
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end

   # accessor methods
   def getWidth
      @width
   end
   def getHeight
      @height
   end

   # setter methods
   def setWidth=(value)
      @width = value
   end
   def setHeight=(value)
      @height = value
   end
end

# create an object
box = Box.new(10, 20)

# let us freez this object
box.freeze
if( box.frozen? )
   puts "Box object is frozen object"
else
   puts "Box object is normal object"
end

# now try using setter methods
box.setWidth = 30
box.setHeight = 50

# use accessor methods
x = box.getWidth()
y = box.getHeight()

puts "Width of the box is : #{x}"
puts "Height of the box is : #{y}"

执行上述代码后,将生成以下结果 −

Box object is frozen object
test.rb:20:in `setWidth=': can't modify frozen object (TypeError)
   from test.rb:39

Class Constants

您可以通过将直接数字或字符串值分配给一个变量来定义类中的常量,而该变量不用 @ 或 @@ 定义。约定上,我们将常量名称保持为大写。

一旦定义了常量,您就不能更改它,但是您可以直接在类中访问常量,就像访问变量一样,但是如果您想在类外访问常量,则必须像下面示例中所示使用 classname::constant

#!/usr/bin/ruby -w

# define a class
class Box
   BOX_COMPANY = "TATA Inc"
   BOXWEIGHT = 10
   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end
   # instance method
   def getArea
      @width * @height
   end
end

# create an object
box = Box.new(10, 20)

# call instance methods
a = box.getArea()
puts "Area of the box is : #{a}"
puts Box::BOX_COMPANY
puts "Box weight is: #{Box::BOXWEIGHT}"

执行上述代码后,将生成以下结果 −

Area of the box is : 200
TATA Inc
Box weight is: 10

类常量可被继承并且可以像实例方法一样被覆盖。

Create Object Using Allocate

当您想在不调用其构造函数 initialize (即使用 new 方法)的情况下创建对象时,可能会出现这种情况,在这种情况下,您可以调用 allocate,它将为您创建一个未初始化的对象,如下例所示 -

#!/usr/bin/ruby -w

# define a class
class Box
   attr_accessor :width, :height

   # constructor method
   def initialize(w,h)
      @width, @height = w, h
   end

   # instance method
   def getArea
      @width * @height
   end
end

# create an object using new
box1 = Box.new(10, 20)

# create another object using allocate
box2 = Box.allocate

# call instance method using box1
a = box1.getArea()
puts "Area of the box is : #{a}"

# call instance method using box2
a = box2.getArea()
puts "Area of the box is : #{a}"

执行上述代码后,将生成以下结果 −

Area of the box is : 200
test.rb:14: warning: instance variable @width not initialized
test.rb:14: warning: instance variable @height not initialized
test.rb:14:in `getArea': undefined method `*'
   for nil:NilClass (NoMethodError) from test.rb:29

Class Information

如果类定义是可执行代码,这意味着它们在某个对象的上下文中执行:self 必须引用某些东西。让我们找出是什么。

#!/usr/bin/ruby -w

class Box
   # print class information
   puts "Type of self = #{self.type}"
   puts "Name of self = #{self.name}"
end

执行上述代码后,将生成以下结果 −

Type of self = Class
Name of self = Box

这意味着类定义以该类为当前对象执行。这意味着在执行方法定义期间将可以使用元类及其超类中的方法。