1. 概述

在 Scala 中,构造器是一种特殊的方法,用于初始化对象。当我们需要创建某个类的对象时,就会调用该类的构造器。构造器通常用于设置对象属性的初始值或默认值。

2. 构造器简介

构造器定义了类的基本蓝图,并可以强制要求在创建对象时必须提供某些值:

class Employee(name: String, email: String)

我们定义了一个 Employee 类,其构造器接受两个参数:一个类型为 Stringname 和一个类型为 Stringemail。通过这个构造器,我们强制规定每个员工对象都必须拥有姓名和邮箱。任何想要创建 Employee 实例的人都必须提供这两个值。

现在假设我们要为这个类增加一个角色(role)属性。角色可以是 0(代表普通员工)或 1(代表管理者)等。我们可以在构造器中添加一个 role 参数:

class Employee(name: String, email: String, role: Int)

但问题来了:如果我们不希望用户随意指定角色,或者传入无效的角色值怎么办?当前的构造器允许任何人随意指定角色,这是我们不想看到的。

因此,我们希望提供一种方式来创建员工对象,而不需要外部直接指定角色。这时候就可以使用 私有(private)受保护(protected) 构造器。

3. 私有构造器(Private Constructor)

当我们不想让用户通过 new 关键字直接实例化类时,可以将构造器声明为私有。取而代之的是,我们提供其他方式来创建类的实例。

有些构造器的实现非常复杂,对于类的功能至关重要。设计者通常会将这些构造器设为私有,并对外提供更易读、更安全的工厂方法来创建实例。

注意:不能继承拥有私有构造器的类。

一个典型的例子是 Scala 标准库中的 Vector 类。它的构造器是私有的,因此我们不能通过 new 关键字直接实例化它,而是使用 Vector.apply 方法来创建实例。

回到我们的 Employee 类,我们可以通过在类名后添加 private 关键字将其构造器设为私有:

class Employee private(name: String, email: String, role: Int)

此时,如果我们尝试使用 new 关键字创建 Employee 实例,Scala REPL 会报错:

scala> val employee = new Employee("John Doe", "[email protected]")
<console>:12: error: constructor Employee in class Employee cannot be accessed in object $iw
       val employee = new Employee("John Doe", "[email protected]")

通过这种方式,我们确保了不能通过 new 关键字直接创建 Employee 实例。

3.1. 实例创建方式

构造器被设为私有后,第一个问题就是:如何创建类的实例?

一种常见的做法是定义一个伴生对象(companion object),按照单例模式来创建类的实例。

伴生对象可以访问其对应类的私有构造器。

我们可以为 Employee 类定义一个伴生对象:

class Employee private(name: String, email: String, role: Int)

object Employee {
  def createWorker(name: String, email: String): Employee = new Employee(name, email, 0)
  def createManager(name: String, email: String): Employee = new Employee(name, email, 1)
}

val worker = Employee.createWorker("John Doe", "[email protected]")
val manager = Employee.createManager("John Smith", "[email protected]")

这样,我们不再使用 new 关键字,而是通过调用 Employee.createWorkerEmployee.createManager 来创建实例。

4. 受保护构造器(Protected Constructor)

在上一节中我们提到,拥有私有构造器的类不能被继承。但如果我们既想限制构造器的访问,又希望这个类可以被继承,该怎么办呢?

这时候可以使用 受保护构造器(protected constructor)

受保护构造器与私有构造器类似,不能通过 new 关键字直接实例化,但可以被继承。

在我们的例子中,与其使用伴生对象的方法,不如定义 WorkerManager 类来继承 Employee 类:

class Employee protected(name: String, email: String, role: Int)
class Worker(name: String, email: String) extends Employee(name, email, 0)
class Manager(name: String, email: String) extends Employee(name, email, 1)

val worker = new Worker("John Doe", "[email protected]")
val manager = new Manager("John Smith", "[email protected]")

通过使用 protected 构造器,我们既保护了构造器的私密性,又允许类被继承。我们可以使用 new 关键字创建 WorkerManager 实例,但不能创建 Employee 实例。

5. 总结

在这篇文章中,我们学习了如何在 Scala 中定义构造器,并通过 privateprotected 关键字控制构造器的访问权限。我们还比较了私有构造器和受保护构造器的区别:

  • 私有构造器:不能通过 new 创建实例,也不能被继承。
  • 受保护构造器:不能通过 new 创建实例,但可以被继承。

本文的完整源码可以在 GitHub 上找到。


原始标题:Private and Protected Constructors in Scala