Ruby - 面向对象


Ruby 是一种纯粹的面向对象语言,一切对于 Ruby 来说都是对象。Ruby 中的每个值都是一个对象,甚至是最原始的东西:字符串、数字甚至 true 和 false。即使类本身也是一个对象,它是Class类的实例。本章将带您了解与面向对象 Ruby 相关的所有主要功能。

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

Ruby 类定义

当您定义类时,您就定义了数据类型的蓝图。这实际上并没有定义任何数据,但它确实定义了类名的含义,即该类的对象将由什么组成以及可以对此类对象执行哪些操作。

类定义以关键字class开头,后跟类名,并以end分隔。例如,我们使用关键字 class 定义 Box 类,如下所示 -

class Box
   code
end

名称必须以大写字母开头,并且按照惯例,包含多个单词的名称将与每个单词大写且没有分隔符 (CamelCase) 一起运行。

定义 Ruby 对象

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

box1 = Box.new
box2 = Box.new

初始化方法

初始化方法是标准 Ruby 类方法,其工作方式与其他面向对象编程语言中的构造函数几乎相同。当您想要在创建对象时初始化某些类变量时,initialize 方法非常有用。此方法可能采用参数列表,并且像任何其他 ruby​​ 方法一样,它前面会有def关键字,如下所示 -

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

实例变量

实例变量是类属性的一种,一旦使用类创建对象,它们就成为对象的属性。每个对象的属性都是单独分配的,并且不与其他对象共享值。它们可以在类内使用 @ 运算符来访问,但要在类外访问它们,我们可以使用公共方法,这些方法称为访问器方法。如果我们采用上面定义的Box类,那么 @width 和 @height 就是 Box 类的实例变量。

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

访问器和设置器方法

为了使变量可以从类外部使用,必须在访问器方法中定义它们,这些访问器方法也称为 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 方法从类外部设置这些变量的值的方法,其定义如下 -

#!/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

实例方法

实例方法的定义方式与我们使用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

类方法和变量

变量是一个变量,在类的所有实例之间共享。换句话说,变量有一个实例,并且由对象实例访问。类变量以两个 @ 字符 (@@) 为前缀。类变量必须在类定义中初始化,如下所示。

类方法是使用def self.methodname()定义的,它以结束分隔符结尾,并且将使用类名称作为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

to_s 方法

您定义的任何类都应该有一个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)

访问控制

Ruby 在实例方法级别提供三个级别的保护,可能是public、private 或 protected。Ruby 不对实例和类变量应用任何访问控制。

  • 公共方法- 任何人都可以调用公共方法。默认情况下,方法是公共的,但初始化除外,它始终是私有的。

  • 私有方法- 私有方法无法访问,甚至无法从类外部查看。只有类方法可以访问私有成员。

  • 受保护的方法- 受保护的方法只能由定义类及其子类的对象调用。访问权保留在家庭内部。

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

#!/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)

类继承

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

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

创建类时,程序员可以指定新类继承现有类的成员,而不是编写全新的数据成员和成员函数。这个现有类称为基类或超类,新类称为派生类或子类

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

方法重写

虽然您可以在派生类中添加新功能,但有时您希望更改父类中已定义方法的Behave。您可以简单地通过保持方法名称相同并覆盖该方法的功能来实现此目的,如下例所示 -

#!/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()

运算符重载

我们希望 + 运算符使用 + 执行两个 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

冷冻物体

有时,我们想要防止对象被更改。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

类常量

您可以通过将直接数字或字符串值分配给变量来定义类内的常量,该变量是在不使用 @ 或@@ 的情况下定义的。按照惯例,我们将常量名称保留为大写。

一旦定义了常量,您就无法更改其值,但可以像变量一样直接在类内部访问常量,但如果您想访问类外部的常量,则必须使用 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

类常量是继承的,并且可以像实例方法一样被重写。

使用分配创建对象

可能有一种情况,您想要创建一个对象而不调用其构造函数初始化,即使用 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

班级信息

如果类定义是可执行代码,这意味着它们在某个对象的上下文中执行: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

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