不可变的类和子类

时间:2015-07-03 21:36:10

标签: java

我正在尝试学习可变/不可变的类,我遇到了this post

提供的部分答案是:

  

如果要强制实现不变性,则不能拥有子类。看到   例如java.lang.String,这是一个最终类,原因如下:   为了防止人们对String进行子类化以使其变得可变。

好吧,我明白了,但是,您将如何处理这个问题。假设您被赋予了创建3个Employee类,Accountant,ITDepartment和QualityAssurance的任务。现在,您可以创建一个名为Employee的抽象类,其中包含所有人共享的常用方法(员工ID,姓名,工资等),但是,您的类不再是不可变的。

使用Java,那你怎么解决这个问题呢?你会创建3个类,使它们成为最终类,并且不实现抽象方法吗? (所以,没有任何子类)或者你会使用一个接口,并只提供getter?

3 个答案:

答案 0 :(得分:4)

  

如果要强制实现不变性,则不能拥有子类。

这几乎是正确的,但并非完全如此。重申一下:

  

如果要强制实现不变性,则必须确保所有子类都是不可变的。

允许子类化的问题在于,通常任何可以创作类的人都可以继承任何公共非最终类。

但是所有子类都必须调用其超类的构造函数之一。包私有构造函数只能由同一包中的子类调用。

如果您seal packages以便控制包中的哪些类,则可以限制子类化。首先定义一个你想要子类的类:

public abstract class ImmutableBaseClass {
  ImmutableBaseClass(...) {
    ...
  }
}

由于所有子类都必须能够访问超级构造函数,因此可以确保您定义的包中的所有子类遵循不可变的规则。

public final class ImmutableConcreteClass extends ImmutableBaseClass {
  public ImmutableConcreteClass(...) {
    super(...);
  }
}

要将此应用于您的示例,

public abstract class Employee {
  private final Id id;
  private final Name name;

  // Package private constructor in sub-classable class.
  Employee(Id id, Name name, ...) {
    // Defensively copy as necessary.
  }
}

public final class Accountant extends Employee {
  // Public constructos allowed in final sub-classes.
  public Accountant(Id id, Name name, ...) {
    super(id, name, ...);  // Call to super works from same package.
  }
}

public final class ITWorker extends Employee {
  // Ditto.
  public ITWorker(Id id, Name name, ...) {
    super(id, name, ...);
  }
}

答案 1 :(得分:2)

值得思考为什么不可变性是可取的 - 通常是因为你有数据需要假设不会改变。 执行此操作的一种方法是使用包含特定于子类的详细信息的非最终成员创建最终的Employee类:

public final class Employee {
    private final long employeeId;
    private final String firstName;
    private final String lastName;
    private final DepartmentalDetails details;

    public Employee(long employeeId, String firstName, String lastName,
            DepartmentalDetails details) {
        super();
        this.employeeId = employeeId;
        this.firstName = firstName;
        this.lastName = lastName;
        this.details = details;
    }
}

abstract class DepartmentalDetails {
}

final class AccountantDetails extends DepartmentalDetails {
    // Things specific to accountants
}

final class ITDetails extends DepartmentalDetails{
    // Things specific to IT
}

final class QualityAssuranceDetails extends DepartmentalDetails{
    // Things specific to QA
}

这在技术上是不可变的(因为实现者可以编写DepartmentalDetails的可变实现),但它 封装了可变性,因此它提供了相同的好处,同时允许一些可扩展性。 (这与composition vs inheritance的概念有关,虽然我不相信这种模式通常是如何使用的。)

我认为值得考虑的另一种可能性 - 你可以按照你的建议制作三个子类,使它们都是最终的,并在抽象类上贴上一个大评论,说所有的实现都应该是不可变的。它不是防弹的,但在一个小型开发项目中,复杂性节省可能值得冒小风险。

答案 2 :(得分:2)

java.lang.String很特别,非常特别 - 它是一种在任何地方使用的基本类型。特别是,java安全框架在很大程度上依赖于字符串,因此每个人都看到String的相同内容是至关重要的,即字符串必须是不可变的(即使在不安全的发布下!)(不幸的是很多人盲目地应用这些严格的要求字符串到他们自己的类,通常是不合理的)

即使如此,如果String可以被子类化,那么它并不是什么大问题,只要String中的所有方法都是final,所以子类不能弄乱String应该是什么样的。如果我接受一个String,你给我一个String的子类,我就不在乎你在子类中做了什么;只要String超类的内容和行为没有调整。

当然,作为一种基本类型,将String标记为最终以避免所有混淆是明智的。

在您的用例中,您可以拥有一个抽象的Employee,并确保所有子类都实现为不可变的。在运行时,任何Employee对象都必须属于一个具体的子类,它是不可变的。

如果,您无法控制员工的子类,并且您怀疑他们要么是不知道自己在做什么的蠢货,要么是想要造成麻烦的坏人,您至少可以保护员工中的部分那个子类不能弄乱它。例如,无论子类做什么,Employee的名称都是不可变的 -

final String name;

protected Employee(String name) { this.name = name; }

public final String getName(){ return name; }  // final method!

然而,大多数程序员在大多数应用程序中这种防御性设计通常是不合理的。我们不必如此偏执。 99%的编码是合作的。如果有人真的需要覆盖某些东西,那就没什么大不了的。我们99%的人都没有编写像String或Collection这样的核心API。不幸的是,许多布洛赫的建议都基于这种用例。对于大多数程序员来说,这些建议并没有多大帮助,特别是新的程序员,可以制作内部应用程序。

相关问题