Ruby 面向对象
Ruby 面向对象编程 (Object-Oriented Programming, OOP)
Section titled “Ruby 面向对象编程 (Object-Oriented Programming, OOP)”Ruby 是一种纯面向对象编程 (OOP) 语言。在 Ruby 中,一切都是对象 (Object),包括像数字 (Numbers)、字符串 (Strings) 这样的原始数据类型,甚至 true、false 和 nil。类 (Classes) 本身也是对象(Class 类的实例)。本章将探讨 Ruby 中的核心 OOP 概念。
类是创建对象的蓝图 (Blueprint)。它封装了数据(属性或实例变量 Instance Variables)和操作这些数据的方法 (Methods)(行为 Behaviors)。
Ruby 类定义 (Class Definition)
Section titled “Ruby 类定义 (Class Definition)”定义一个类会创建一个新的数据类型。它规定了该类的对象由什么组成(其状态 State)以及可以对它执行哪些操作(其行为 Behavior)。
类定义以 class 关键字开头,后跟类名(必须以大写字母开头),并以 end 关键字结束。按照惯例,多单词的类名使用驼峰命名法 (CamelCase)(例如,CustomerAccount)。
class Box # code for the class goes hereend定义 Ruby 对象 (Objects) / 实例 (Instances)
Section titled “定义 Ruby 对象 (Objects) / 实例 (Instances)”对象是类的实例 (Instances)。您可以使用 new 方法创建一个对象(实例化一个类),new 方法是所有类都可用的类方法 (Class Method)。
box1 = Box.newbox2 = Box.newinitialize 方法 (构造方法)
Section titled “initialize 方法 (构造方法)”initialize 方法是 Ruby 中一个特殊的实例方法 (Instance Method)。它充当构造方法 (Constructor),当您使用 ClassName.new 创建新对象时会自动调用。它用于设置对象的初始状态,通常通过为实例变量赋值。它可以接受参数 (Arguments)。
class Box def initialize(width, height) @width = width # @width 是一个实例变量 @height = height # @height 是一个实例变量 endend
# 当调用 Box.new(10, 20) 时,initialize 方法被调用,w=10, h=20。实例变量 (Instance Variables)
Section titled “实例变量 (Instance Variables)”实例变量以 @ 符号开头(例如,@width)。它们保存特定类实例(对象)的数据。每个对象都拥有自己的实例变量副本。在类内部,可以直接使用 @variable_name 访问它们。要从类外部访问或修改它们,通常使用存取器 (Accessor) 和设置器 (Setter) 方法。
class Box def initialize(w, h) # 赋值给实例变量 @width = w @height = h endend存取器 (Accessor) 和设置器 (Setter) 方法 (Getter 和 Setter)
Section titled “存取器 (Accessor) 和设置器 (Setter) 方法 (Getter 和 Setter)”为了允许从类外部对对象的实例变量进行受控访问,您可以定义存取器(getter)方法来读取它们的值,以及设置器(setter 或 mutator)方法来更改它们。Ruby 提供了创建这些常用方法的便捷快捷方式:
attr_reader :variable_name_symbol: 创建 getter 方法。attr_writer :variable_name_symbol: 创建 setter 方法。attr_accessor :variable_name_symbol: 同时创建 getter 和 setter 方法。
使用 attr_accessor 的示例:
#!/usr/bin/env ruby
class Box # 创建 getter (width, height) 和 setter (width=, height=) 方法 attr_accessor :width, :height
def initialize(w, h) @width = w @height = h end
def display_dimensions puts "Box dimensions - Width: #{@width}, Height: #{@height}" endend
# 创建一个对象box = Box.new(10, 20)box.display_dimensions
# 使用存取器 (getter) 方法puts "Initial width: #{box.width}"
# 使用设置器方法box.width = 30box.height = 50
puts "Updated width: #{box.width}"box.display_dimensions运行上述代码,将产生:
Box dimensions - Width: 10, Height: 20Initial width: 10Updated width: 30Box dimensions - Width: 30, Height: 50手动定义 getter 和 setter(由于 attr_* 辅助方法,现在不太常用):
#!/usr/bin/env ruby
class Box def initialize(w, h) @width = w @height = h end
# width 的 Getter def width @width end
# width 的 Setter def width=(value) @width = value end
# height 的 Getter def height @height end
# height 的 Setter def height=(value) @height = value endend
box = Box.new(10, 20)box.width = 30 # 调用 width=(30)puts box.height # 调用 height()实例方法 (Instance Methods)
Section titled “实例方法 (Instance Methods)”实例方法定义对象的行为。它们使用 def 关键字在类内定义,并操作被调用对象的数据(实例变量)。它们通过类的实例来调用。
#!/usr/bin/env ruby
class Box attr_accessor :width, :height
def initialize(w, h) @width = w @height = h end
# 计算面积的实例方法 def area @width * @height endend
# 创建一个对象box = Box.new(10, 20)
# 调用实例方法a = box.areaputs "Area of the box is: #{a}"运行结果:
Area of the box is: 200类方法 (Class Methods) 和类变量 (Class Variables)
Section titled “类方法 (Class Methods) 和类变量 (Class Variables)”类变量(例如,@@count)在类的所有实例、类本身及其子类之间共享。它们以 @@ 为前缀,必须在类定义内初始化。
类方法通过在类定义内给方法名加上 self. 前缀(例如,def self.method_name)或 ClassName. 来定义。它们在类本身上调用,而不是在实例上调用,通常操作类变量或作为实例的工厂 (Factories)。
#!/usr/bin/env ruby
class Box # 类变量 @@count = 0
attr_accessor :width, :height
def initialize(w, h) @width = w @height = h @@count += 1 end
# 类方法 def self.print_count puts "Box count is: #{@@count}" endend
# 创建两个对象box1 = Box.new(10, 20)box2 = Box.new(30, 100)
# 调用类方法Box.print_count运行结果:
Box count is: 2to_s 方法
Section titled “to_s 方法”to_s 实例方法用于返回对象的字符串表示。它会被 puts 等方法或在字符串插值中使用对象时自动调用。为您的类定义自定义的 to_s 方法是一个好习惯,可以提供有意义的字符串表示。
#!/usr/bin/env ruby
class Box attr_reader :width, :height # 本例只需要 getter
def initialize(w, h) @width = w @height = h end
# 自定义 to_s 方法 def to_s "(w:#{@width}, h:#{@height})" # 对象的字符串格式化 endend
# 创建一个对象box = Box.new(10, 20)
# puts 自动调用 to_sputs "String representation of box is: #{box}"运行结果:
String representation of box is: (w:10, h:20)访问控制 (Access Control)(public, private, protected)
Section titled “访问控制 (Access Control)(public, private, protected)”Ruby 为实例方法提供了三个级别的访问控制。访问控制不直接应用于实例变量或类变量;对它们的访问通常通过方法进行管理。
- Public (公开): Public 方法可以被任何人从任何地方调用。方法默认是 public 的(除了
initialize,它是隐式 private 的)。 - Private (私有): Private 方法只能由同一类定义内的其他实例方法调用,并且只能通过隐式接收者 (Implicit Receiver)(
self)。它们不能通过显式接收者 (Explicit Receiver) 调用(例如,object.private_method会失败,即使在类内部也是如此)。initialize始终是 private 的。 - Protected (受保护): Protected 方法可以由定义该类的实例方法及其子类的实例方法调用。与 private 方法不同,它们可以使用显式接收者调用,前提是该接收者是
self或同一类或子类的实例。
访问控制关键字 (public、private、protected) 应用于它们在类定义中声明之后的方法,直到遇到另一个访问控制关键字或类定义结束。它们也可以接受方法名(作为符号 Symbol)作为参数,以设置特定方法的可见性。
#!/usr/bin/env ruby
class Box attr_accessor :width, :height
def initialize(w, h) @width = w @height = h end
# 默认是 public def calculate_area internal_width * internal_height # 调用 private 方法 end
# 演示 protected 的实例方法 def compare_area(other_box) # 如果 other_box 是 Box 或其子类,则可以在其上调用 protected 方法 other_box.display_protected_area # 这对于 protected 是可行的 # 对于本例,我们直接使用它的 public calculate_area self.calculate_area > other_box.calculate_area end
protected
def display_protected_area puts "Protected: Area is #{internal_width * internal_height}" end
private
# private getter 方法(示例,通常 attr_reader 会是 public 或 protected) def internal_width @width end
def internal_height @height endend
box = Box.new(10, 20)
# 调用 public 方法area = box.calculate_areaputs "Area of the box is: #{area}"
# 尝试直接从外部调用 private 或 protected 方法(将导致错误)begin # box.internal_width # 这将引发 NoMethodError(private 方法被调用) # box.display_protected_area # 这将引发 NoMethodError(protected 方法被调用)rescue NoMethodError => e puts "Error: #{e.message}"end
box2 = Box.new(5,10)puts "Box 1 is larger than Box 2: #{box.compare_area(box2)}"# box.display_protected_area # 仍然不能直接从外部调用# box2.display_protected_area # 这个也不行运行上述代码时,尝试直接从外部调用 private/protected 方法将导致 NoMethodError。公共方法 calculate_area 可以正常工作。
Area of the box is: 200Box 1 is larger than Box 2: true类继承 (Class Inheritance)
Section titled “类继承 (Class Inheritance)”继承是 OOP 的基本概念,一个类(子类 Subclass 或派生类 Derived Class)可以继承另一个类(超类 Superclass 或基类 Base Class)的属性 (Attributes) 和方法。这促进了代码重用,并建立了“is-a”(是)关系(例如,BigBox 是一种 Box)。
Ruby 支持单继承(一个类只能继承自一个超类)。然而,Ruby 通过使用模块 (Modules) 进行 Mixin 来实现类似于多重继承 (Multiple Inheritance) 的特性。Mixin 允许一个类包含一个或多个模块的方法。
要定义子类,使用 < 符号,后跟超类名。
#!/usr/bin/env ruby
class Box # 超类 attr_accessor :width, :height
def initialize(w, h) @width = w @height = h end
def area @width * @height endend
# BigBox 是 Box 的子类class BigBox < Box # BigBox 继承了 Box 的 initialize, width, height, 和 area
# 添加 BigBox 特有的新实例方法 def print_area # @width 和 @height 可访问,因为它们是实例变量,并且 BigBox 是 Box。 # 如果它们在 Box 中是 private 方法,直接访问可能会有所不同。 # 'area' 方法是继承来的,并且是 public。 puts "Big box area is: #{area}" # 使用继承的 area 方法 endend
# 创建子类对象big_box = BigBox.new(100, 200)
# 调用继承方法(通过 print_area)big_box.print_area
# 直接调用继承方法puts "Directly calling area: #{big_box.area}"运行结果:
Big box area is: 20000Directly calling area: 20000方法重写 (Method Overriding)
Section titled “方法重写 (Method Overriding)”子类可以提供其超类中已定义的实例方法的特定实现。这称为方法重写 (Method Overriding)。子类中重写的方法必须与超类中的方法具有相同的名称。
您可以使用 super 关键字在重写方法内部调用超类版本的方法。
#!/usr/bin/env ruby
class Box def initialize(w, h) @width = w @height = h end
def display_info "Box with width #{@width} and height #{@height}" endend
class ColoredBox < Box attr_accessor :color
def initialize(w, h, color) super(w, h) # 调用 Box 的 initialize 方法 @color = color end
# 重写 display_info 方法 def display_info # 调用超类的 display_info 方法并添加更多信息 "#{super}, and color #{@color}" endend
colored_box = ColoredBox.new(10, 20, "Red")puts colored_box.display_info运行结果:
Box with width 10 and height 20, and color Red运算符重载 (Operator Overloading)
Section titled “运算符重载 (Operator Overloading)”Ruby 允许您重新定义标准运算符(如 +、-、*、[])如何对您的类对象进行操作。这通过定义与运算符名称对应的方法来实现。例如,要重载 + 运算符,您需要定义一个名为 + 的方法。
class Vector2D attr_accessor :x, :y
def initialize(x, y) @x, @y = x, y end
# 重载 + 运算符进行向量加法 def +(other_vector) # 确保 other_vector 也是 Vector2D 对象以保证类型安全(可选,但推荐) raise ArgumentError, "Can only add Vector2D objects" unless other_vector.is_a?(Vector2D) Vector2D.new(@x + other_vector.x, @y + other_vector.y) end
# 重载一元 - 运算符用于向量分量取反 def -@ Vector2D.new(-@x, -@y) end
# 重载 * 运算符进行标量乘法 def *(scalar) raise ArgumentError, "Can only multiply by a Numeric" unless scalar.is_a?(Numeric) Vector2D.new(@x * scalar, @y * scalar) end
def to_s "(#{x}, #{y})" endend
v1 = Vector2D.new(2, 3)v2 = Vector2D.new(5, 1)
sum_vector = v1 + v2 # 调用 v1.+(v2)puts "v1 + v2 = #{sum_vector}" # 输出: (7, 4)
negated_v1 = -v1 # 调用 v1.-@()puts "-v1 = #{negated_v1}" # 输出: (-2, -3)
scaled_v1 = v1 * 3 # 调用 v1.*(3)puts "v1 * 3 = #{scaled_v1}" # 输出: (6, 9)冻结对象 (Freezing Objects)
Section titled “冻结对象 (Freezing Objects)”freeze 方法(所有对象都可用,因为它来自 Object)可以阻止对象被修改。一旦对象被冻结,尝试更改其实例变量或内部状态将引发 FrozenError(在旧版 Ruby 中为 RuntimeError)。您可以使用 frozen? 方法检查对象是否已冻结。
#!/usr/bin/env ruby
class Config attr_accessor :setting def initialize(val) @setting = val endend
app_config = Config.new("initial_value")puts "Initially frozen? #{app_config.frozen?}" # => false(初始是否冻结?)
app_config.setting = "new_value" # 可修改puts "Setting after change: #{app_config.setting}"
app_config.freeze # 冻结对象puts "Now frozen? #{app_config.frozen?}" # => true(现在是否冻结?)
begin app_config.setting = "another_value" # 这将引发错误rescue FrozenError => e puts "Error: #{e.message}"end
# 注意:冻结对象通常是浅冻结(shallow freeze)。# 如果实例变量引用了另一个可变对象(例如,一个数组),那么那个对象本身并不会被冻结,除非你也明确地冻结它。# 例如:# mutable_array = [1, 2]# app_config.some_array = mutable_array# app_config.freeze# app_config.some_array << 3 # 如果 mutable_array 没有被冻结,这条语句仍然可以修改它。此代码演示了冻结对象:
Initially frozen? falseSetting after change: new_valueNow frozen? trueError: can't modify frozen Config类常量 (Class Constants)
Section titled “类常量 (Class Constants)”类内部的常量通过为以大写字母开头的名称赋值来定义。它们通常用于不应更改的值。按照惯例,常量名称使用 ALL_CAPS_WITH_UNDERSCORES 格式。
常量可以在类内部直接使用名称访问。从类外部,它们使用类名后跟作用域解析运算符 :: 访问(例如,ClassName::CONSTANT_NAME)。尽管 Ruby 在您尝试重新赋值常量时会发出警告,但它技术上允许这样做(这通常是混淆的来源)。
#!/usr/bin/env ruby
class Box BOX_COMPANY = "ACME Inc." DEFAULT_MATERIAL = "Cardboard"
attr_reader :material
def initialize(material = DEFAULT_MATERIAL) @material = material end
def display_company puts "This box is from #{BOX_COMPANY} made of #{@material}." endend
box = Box.newbox.display_company
custom_box = Box.new("Plastic")custom_box.display_company
puts "Default material for all boxes: #{Box::DEFAULT_MATERIAL}"运行结果:
This box is from ACME Inc. made of Cardboard.This box is from ACME Inc. made of Plastic.Default material for all boxes: Cardboard类常量会被子类继承,并且可以被重写(尽管这也会生成警告,通常不鼓励这样做)。
使用 allocate 创建对象
Section titled “使用 allocate 创建对象”allocate 类方法创建一个类的实例,但不调用其 initialize 方法。生成的对象未初始化;其实例变量将为 nil。在典型的应用代码中很少需要这样做,通常用于更高级的场景,例如对象反序列化或某些元编程任务。
#!/usr/bin/env ruby
class Box attr_accessor :width, :height
def initialize(w, h) puts "Initialize called!" @width, @height = w, h end
def area # 如果 width 或 height 为 nil,这将导致错误 @width * @height endend
puts "Creating box1 with new:"box1 = Box.new(10, 20)puts "Area of box1: #{box1.area}"
puts "\nCreating box2 with allocate:"box2 = Box.allocate # initialize 没有被调用
puts "box2 width: #{box2.width.inspect}" # => nil(box2 的 width)puts "box2 height: #{box2.height.inspect}" # => nil(box2 的 height)
begin puts "Area of box2: #{box2.area}"rescue NoMethodError => e # 尝试在 nil 上调用 * (例如 nil * nil) puts "Error calculating area for box2: #{e.message}"end运行结果类似于:
Creating box1 with new:Initialize called!Area of box1: 200
Creating box2 with allocate:box2 width: nilbox2 height: nilError calculating area for box2: undefined method `*' for nil:NilClass类信息 (内省/Introspection)
Section titled “类信息 (内省/Introspection)”在类定义内部,self 指代类本身。您可以使用标准对象方法在 self 上获取关于类的信息。
#!/usr/bin/env ruby
class MyCoolClass puts "Inside class definition:" puts "Type of self: #{self.class}" # self 是类 MyCoolClass,它的类是 Class puts "Name of self: #{self.name}" # self.name 提供类名字符串 puts "Self inspect: #{self.inspect}" # 对于类来说,与 self.name 相同end加载此文件时,它将产生:
Inside class definition:Type of self: ClassName of self: MyCoolClassSelf inspect: MyCoolClass这表明类定义是可执行代码,并且在其执行期间,当前对象 (self) 是正在定义的类。这使得 Ruby 具备强大的元编程 (Metaprogramming) 能力。