Ruby - 面向对象

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

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


Ruby 类定义

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

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

class Box
   code
end

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


定义 Ruby 对象

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

box1 = Box.new
box2 = Box.new

初始化方法

initialize 方法 是标准的 Ruby 类方法,其工作方式与其他面向对象编程语言中的 constructor 工作方式几乎相同。当您想在创建对象时初始化一些类变量时,initialize 方法很有用。 该方法可以采用参数列表,并且与任何其他 ruby 方法一样,它前面会带有 def 关键字,如下所示 −

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

实例变量

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

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

accessor & setter 方法

为了使变量在类外部可用,它们必须在访问器方法中定义,这些访问器方法也称为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 在实例方法级别为您提供三个级别的保护,可能是公共、私有或受保护。 Ruby 不对实例和类变量应用任何访问控制。

  • Public 方法 − 公共方法可以被任何人调用。 方法默认是公共的,除了初始化,它总是私有的。

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

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

下面是一个简单的例子来展示所有三个访问修饰符的语法 −

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

方法覆盖

虽然您可以在派生类中添加新功能,但有时您希望更改父类中已定义方法的行为。 您可以通过保持方法名称相同并覆盖方法的功能来简单地做到这一点,如下面的示例所示 −

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

类常量是继承的,可以像实例方法一样被覆盖。


使用分配创建对象

可能有一种情况,你想创建一个对象而不调用它的构造函数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

班级信息

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

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