1. 概述
在本教程中,我们将深入探讨 Scala 中的伴生对象(Companion Object),包括其定义、作用以及如何利用它来实现工厂模式和提取器(Extractor)。
2. 类与对象
假设我们有一个 Task
类:
class Task(val description: String) {
private var _status: String = "pending"
def status(): String = _status
}
这是一个基础的 Scala 类。如果我们想创建一个 Task
实例,可以通过调用其主构造函数实现:
val task = new Task("do something")
assert(task.description == "do something")
现在我们给这个类增加一个辅助构造函数,接受 status
参数:
class Task(val description: String) {
private var _status: String = "pending"
def this(description: String, status: String) = {
this(description)
this._status = status
}
def status(): String = _status
}
这样就可以创建带有指定状态的 Task
实例了:
val task = new Task("do something", "started")
assert(task.status == "started")
✅ 我们可以继续添加更多的辅助构造函数,但存在以下两个问题:
- 每次创建对象都要使用
new
关键字,虽然不是大问题,但能省则省; - 所有辅助构造函数都必须首先调用已存在的构造函数,容易导致构造函数“ telescoping(链式调用)”问题;
⚠️ 如果我们想提供更灵活的对象创建方式,比如通过工厂方法来构建对象,是不是会更好?工厂方法相比构造函数有很多优势。
但由于 Scala 没有 static 关键字,因此无法像 Java 那样定义静态工厂方法。不过别急,Scala 提供了另一种机制:单例对象(Singleton Object)。
3. 伴生对象
当一个单例对象与某个类同名且定义在同一文件中时,该对象就被称为这个类的伴生对象(Companion Object)。
让我们为前面定义的 Task
类创建一个伴生对象:
class Task(val description: String) {
private var _status: String = "pending"
def status(): String = _status
}
object Task {
def apply(description: String): Task = new Task(description)
}
在这个伴生对象中,我们定义了一个 apply
方法。在 Scala 中,apply
是一个特殊的方法,允许我们在不显式写出方法名的情况下调用它。
有了这个方法后,我们可以这样创建对象:
val task = Task("do something")
assert(task.description == "do something")
这实际上等价于:
val task = Task.apply("do something")
assert(task.description == "do something")
✅ 更进一步地,我们可以重载 apply
方法,提供多个不同参数的工厂方法:
class Task(val description: String) {
private var _status: String = "pending"
def status(): String = _status
}
object Task {
def apply(description: String): Task = new Task(description)
def apply(description: String, status: String): Task = {
val task = new Task(description)
task._status = status
task
}
}
然后就可以根据需要选择不同的创建方式:
val task = Task("do something", "started")
assert(task.status == "started")
⚠️ 注意:在这个例子中,伴生对象访问了类中的私有字段 _status
。这正是伴生对象的一个重要特性:类和它的伴生对象可以互相访问彼此的私有成员。
此外,伴生对象还可以实现更复杂的 apply
方法,用于创建整个类层次结构中的不同类型实例。例如父类的伴生对象可以根据参数决定返回哪个子类型,对外隐藏具体逻辑,提供统一接口。
❌ 这一点是辅助构造函数做不到的,因为它们只能返回当前类的实例。
4. 提取器(Extractors)
除了 apply
方法,我们还可以在伴生对象中定义 unapply
方法,它作为提取器,可以从对象中提取信息。
举个例子,我们定义一个提取器,返回 Task
的描述和状态:
object Task {
def unapply(task: Task): Tuple2[String, String] = (task.description, task.status())
}
val task = Task("do something")
val (description, status) = Task.unapply(task)
assert(description == "do something")
assert(status == "pending")
💡 unapply
可以返回任意类型的数据,使得我们可以写出强大的模式匹配表达式。
5. 总结
在本文中,我们学习了 Scala 中的伴生对象,并了解了如何使用它来创建灵活的工厂方法。
我们还看到了类和它的伴生对象之间可以互相访问私有成员,以及如何通过 unapply
实现提取器功能。
✅ 伴生对象不仅让代码更简洁优雅,还能帮助我们构建更灵活、更具扩展性的 API。
如需查看完整代码示例,请访问 GitHub 项目地址。