Ruby 类的继承
继承(Inheritance)允许你基于一个现有的类来定义一个新类,并继承其属性和行为。这种强大的机制促进了代码复用,在类之间建立了清晰的层级关系,并使你的程序更加井然有序和易于维护。
通过理解继承,你能在代码中更准确地对现实世界中的关系进行建模,创建一个让“特殊对象”能共享“通用对象”特征的结构,从而彻底告别重复代码。
本章将基于你之前掌握的类、对象和方法的知识,深入探讨继承在 Ruby 中是如何运作的,它的优势是什么,以及如何有效地实现它来构建健壮且可扩展的应用程序。
1. 什么是继承?
从核心上讲,继承是关于创建一个类层级结构,其中一个类(子类或派生类)可以从另一个类(父类或超类)派生出属性和行为。这种关系通常被称为 “是一个 (is-a)” 关系。
例如,一只狗 (Dog) 是一个动物 (Animal),一辆汽车 (Car) 是一个交通工具 (Vehicle)。子类会继承其父类所有可公开访问的属性(实例变量)和方法,同时它还可以添加自己独有的属性和方法,甚至修改(重写)继承来的内容。
1.1 “是一个 (Is-A)” 关系
“是一个”关系对于正确应用继承至关重要。它意味着子类的实例完全可以被当作父类的实例来对待。
- 一辆轿车 (Sedan) 是一辆汽车 (Car)。
- 一辆汽车 (Car) 是一个交通工具 (Vehicle)。
- 一名开发者 (Developer) 是一名员工 (Employee)。
如果“是一个”的关系不成立,那么继承可能就不是最合适的设计模式。例如,一个车轮 (Wheel) 不是一辆汽车 (Car);相反,是一辆汽车拥有 (has-a) 一个车轮。这暗示了一种“拥有”的关系,通常使用组合 (Composition)(即一个对象将另一个对象作为属性包含进来)来建模,而不是继承。
2. 继承的优势
在软件开发中,继承提供了几个显著的优势:
- 代码复用 (Code Reusability): 这是最立竿见影的好处。与其在多个类中编写相同的代码,不如在父类中定义一次通用的属性和方法,所有子类就会自动继承它们。这减少了冗余,使代码库更小、更易于管理。
- 可维护性 (Maintainability): 当你需要更新某个通用行为时,只需在父类中修改一次,所有子类都会自动反映出该更改。这极大地简化了维护工作并降低了出错的几率。
- 可扩展性 (Extensibility): 继承使你的应用程序更容易扩展。你可以添加继承自现有类的新(专用)类,而无需修改现有的父类代码,这遵循了开闭原则 (Open/Closed Principle)(对扩展开放,对修改关闭)。
- 多态基础 (Polymorphism): 虽然本章不会深入探讨多态(这是一个更高级的 OOP 概念),但继承为它奠定了基础。多态(意为“多种形态”)允许不同类的对象被当作公共父类的对象来处理。这意味着你可以编写操作父类类型的代码,而它能正确作用于任何子类,从而产生更灵活、更通用的代码。
3. 现实世界类比
3.1 交通工具系统
想象你正在设计一个管理不同类型交通工具的系统。所有交通工具都有一些共同特征:它们都有品牌 (make)、型号 (model),并且都能启动引擎 (start_engine)。然而,汽车 (Car) 还可以驾驶 (drive),而摩托车 (Motorcycle) 可以骑行 (ride)。
- 你可以创建一个
Vehicle(交通工具)类,定义通用属性和方法。 - 然后,创建继承自
Vehicle的Car和Motorcycle类。它们会自动获得品牌、型号和启动引擎的行为,你只需添加它们特定的drive或ride行为。 - 如果以后引入
Truck(卡车)类,它也继承自Vehicle,从而确保所有交通工具类型的一致性。
3.2 数字资产管理
考虑一个管理各种数字资产的系统。所有 DigitalAsset(数字资产)对象可能都有 filename(文件名)、size(大小)和 creation_date(创建日期)。然而,Image(图像)资产可能还有 resolution(分辨率)和 display(显示)方法,而 AudioFile(音频文件)可能有 duration(时长)和 play(播放)方法。
通过让 Image 和 AudioFile 作为子类继承 DigitalAsset,既确保了所有资产共享基本特征,又允许针对不同资产类型实现特定功能,避免了代码重复。
4. Ruby 继承:语法与机制
在 Ruby 中,使用一个特殊的符号实现继承非常直截了当。Ruby 支持单继承 (Single Inheritance),这意味着一个类只能从一个直接父类继承。不过,你可以创建一个长长的继承链(A 继承 B,B 继承 C,依此类推)。
4.1 定义父类与子类
要定义一个继承自父类的子类,需要在子类名后面使用 < 符号,紧接着写上父类名。
# 父类 (Superclass)
class Animal
attr_accessor :name, :species
def initialize(name, species)
@name = name
@species = species
end
def speak
"#{name} 发出了声音。"
end
def introduce
"你好,我的名字是 #{name},我是一只 #{species}。"
end
end
# 子类 (Child Class) 继承自 Animal
class Dog < Animal
def initialize(name, breed)
# 调用父类的 initialize 方法
# 在这里,'Dog' 是狗对象的物种 (species)
super(name, "Dog")
@breed = breed # 添加一个专门针对 Dog 的新属性
end
# 狗有自己独特的发声方式
def speak
"汪汪!汪汪!"
end
def fetch(item)
"#{name} 把 #{item} 捡回来了!"
end
def dog_info
"#{name} 是一只 #{@breed}。"
end
end
# Cat 的子类,同样继承自 Animal
class Cat < Animal
def initialize(name, fur_color)
super(name, "Cat")
@fur_color = fur_color
end
# 猫也有自己独特的发声方式
def speak
"喵!"
end
def purr
"#{name} 正在满足地打呼噜。"
end
end在这个例子中:
Animal是父类。Dog和Cat是子类,都继承自Animal。Dog和Cat自动获得了name、species属性以及introduce方法。- 它们还定义了各自的
initialize方法、重写了speak方法,以及各自独有的方法(如fetch、purr)。
5. Object 类:万物之祖
在 Ruby 中,你创建的每个类都隐式地继承自一个名为 Object 的特殊类。如果你没有显式地为你的类指定父类,Ruby 就会假设 Object 是它的父类。Object 提供了许多所有 Ruby 对象都可用的基础方法,例如 to_s(转为字符串)、inspect(详细对象表示)、class(获取对象类名)和 is_a?(检查类型)。
你可以使用 ancestors 方法来验证一个类的完整继承链:
puts Dog.ancestors.inspect # => [Dog, Animal, Object, Kernel, BasicObject]这显示了完整的继承层次结构,包括 Kernel(提供基本功能的模块)和 BasicObject(Ruby 中所有对象的终极根节点)。
6. 方法重写 (Method Overriding)
当子类定义了一个与其父类中同名的方法时,就会发生方法重写。当在子类实例上调用该方法时,执行的是子类的版本,这有效地替换(覆盖)了父类的实现。
在我们的例子中:
Animal有一个speak方法:"#{name} 发出了声音。"Dog重写了speak:"汪汪!汪汪!"Cat重写了speak:"喵!"
my_dog = Dog.new("Buddy", "金毛寻回犬")
my_cat = Cat.new("Whiskers", "三花猫")
generic_animal = Animal.new("Leo", "狮子")
puts my_dog.speak # 输出: 汪汪!汪汪! (Dog 的方法)
puts my_cat.speak # 输出: 喵! (Cat 的方法)
puts generic_animal.speak # 输出: Leo 发出了声音。 (Animal 的方法)7. 使用 super 关键字
通常,当你在子类中重写一个方法时,你仍然希望利用父类版本该方法中的一部分逻辑。这就是 super 关键字派上用场的地方。super 允许你调用直接父类中同名的方法。
使用 super 主要有三种方式:
super(无括号或参数): 将传递给当前方法的所有参数,原封不动地传递给父类的方法。super()(带空括号): 不向父类的方法传递任何参数(即使当前方法接收到了参数)。super(arg1, arg2, ...)(带特定参数): 将你指定的参数传递给父类的方法。
super 最常见的用例是在 initialize 方法中,特别是当子类需要在父类处理完基础设置后,再添加自己的初始化逻辑时。
class Vehicle
attr_accessor :make, :model
def initialize(make, model)
@make = make
@model = model
end
def start_engine
"正在启动 #{make} #{model} 的引擎。"
end
end
class Car < Vehicle
attr_accessor :num_doors
def initialize(make, model, num_doors)
super(make, model) # 调用 Vehicle 的 initialize
@num_doors = num_doors # 添加 Car 特有的属性
end
def start_engine
# 调用 Vehicle 的 start_engine,然后添加汽车特有的逻辑
super + " 汽车引擎发出轰鸣声。"
end
def drive
"正在驾驶有 #{num_doors} 扇门的 #{make} #{model}。"
end
end
my_car = Car.new("丰田", "凯美瑞", 4)
puts my_car.start_engine # 输出: 正在启动 丰田 凯美瑞 的引擎。 汽车引擎发出轰鸣声。
puts my_car.drive # 输出: 正在驾驶有 4 扇门的 丰田 凯美瑞。在这里,Car 的 start_engine 方法首先调用 super 执行父类的逻辑,然后追加了自己的特定消息。这是扩展功能而非完全替换功能的经典模式。
8. 实战应用:构建类层级
让我们应用继承的知识来构建两个不同的类层级结构,演示如何组织通用和特定行为。
8.1 案例 1:Animal 及其后代
# 父类: Animal
class Animal
attr_reader :name, :age
def initialize(name, age)
@name = name
@age = age
end
def eat(food_item)
"#{name} 正在吃 #{food_item}。"
end
def sleep
"#{name} 正在睡觉。"
end
def description
"#{name} 是一只 #{self.class},今年 #{age} 岁。"
end
end
# 子类: Dog
class Dog < Animal
attr_reader :breed # Dog 特有属性
def initialize(name, age, breed)
super(name, age) # 调用 Animal 的 initialize 设置 name 和 age
@breed = breed
end
def speak
"汪汪!汪汪!"
end
def fetch(item)
"#{name} 热情地把 #{item} 捡了回来!"
end
# 重写 description 以包含品种
def description
super + " 它是一只 #{breed}。"
end
end
# 子类: Cat
class Cat < Animal
attr_reader :color # Cat 特有属性
def initialize(name, age, color)
super(name, age)
@color = color
end
def speak
"喵!"
end
def scratch(object)
"#{name} 抓挠了 #{object}。"
end
# 重写 description 以包含毛色
def description
super + " 它有 #{color} 的毛发。"
end
end
my_dog = Dog.new("Buddy", 5, "金毛寻回犬")
my_cat = Cat.new("Whiskers", 3, "三花猫")
puts my_dog.description # 输出: Buddy 是一只 Dog,今年 5 岁。 它是一只 金毛寻回犬。
puts my_dog.speak # 输出: 汪汪!汪汪!
puts my_dog.eat("狗粮") # 输出: Buddy 正在吃 狗粮。8.2 案例 2:Employee 及其专业化
我们来模拟一个简单的员工管理系统。
# 父类: Employee
class Employee
attr_reader :id, :name, :salary
def initialize(id, name, salary)
@id = id
@name = name
@salary = salary
end
def display_info
"工号: #{id}, 姓名: #{name}, 薪水: $#{'%.2f' % salary}"
end
def give_raise(amount)
@salary += amount
"#{name} 的新薪水是 $#{'%.2f' % salary}。"
end
end
# 子类: Manager
class Manager < Employee
attr_reader :department
def initialize(id, name, salary, department)
super(id, name, salary)
@department = department
end
def display_info
super + ", 部门: #{department}"
end
def assign_task(employee, task)
"经理 #{name} 把任务 '#{task}' 分配给了 #{employee.name}。"
end
end
# 子类: Developer
class Developer < Employee
attr_reader :programming_language
def initialize(id, name, salary, language)
super(id, name, salary)
@programming_language = language
end
def display_info
super + ", 编程语言: #{programming_language}"
end
def write_code
"#{name} 正在使用 #{programming_language} 编写代码。"
end
end
manager = Manager.new(101, "Alice Smith", 90000, "市场部")
developer = Developer.new(201, "Bob Johnson", 80000, "Ruby")
puts manager.display_info # 输出: 工号: 101, 姓名: Alice Smith, 薪水: $90000.00, 部门: 市场部
puts developer.display_info # 输出: 工号: 201, 姓名: Bob Johnson, 薪水: $80000.00, 编程语言: Ruby
puts developer.write_code # 输出: Bob Johnson 正在使用 Ruby 编写代码。