I\'m trying to learn about mutable/immutable classes and I came across this post
Part of the answer provided was:
If you want to enforce immu
If you want to enforce immutability, you cannot have subclasses.
This is almost true, but not entirely. To restate it:
If you want to enforce immutability, you must ensure that all sub-classes are immutable.
The problem with allowing subclassing is that normally anyone who can author a class can subclass any public non-final class.
But all subclasses must invoke one of their super-class's constructors. Package-private constructors can only be invoked by subclasses in the same package.
If you seal packages so that you control which classes are in your package, you can constrain subclassing. First define a class you want to subclass:
public abstract class ImmutableBaseClass {
ImmutableBaseClass(...) {
...
}
}
Since all sub-classes have to have access to the super-constructor, you can ensure all the sub-classes in the package you define follow immutable discipline.
public final class ImmutableConcreteClass extends ImmutableBaseClass {
public ImmutableConcreteClass(...) {
super(...);
}
}
To apply this to your example,
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, ...);
}
}
It's worth thinking why immutability is desirable - usually it's because you have data which you need to assume is not going to change. One approach to do this is to make a final Employee class with a non-final member containing the details specific to the subclasses:
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
}
This isn't technically immutable (since an implementer could write a mutable implementation of DepartmentalDetails) but it does encapsulate the mutability, so it gives the same benefits while allowing some extensibility. (This is related to this is the concept of composition vs inheritance, although I don't believe this pattern is how that is normally used.)
One other possibility I think is worth considering - you could make the three subclasses as you suggest, make them all final, and stick a big comment on the abstract class saying all implementations should be immutable. It's not bulletproof, but on a small development project the complexity saving is likely to be worth the tiny risk.
java.lang.String
is special, very special - it's a basic type used everywhere. In particular, java security framework heavily depends on Strings, therefore it is critical that everybody sees the same content of a String, i.e. a String must be immutable (even under unsafe publication!) (Unfortunately a lot of people blindly apply those strict requirements of String to their own classes, often unjustified)
Even so, it's not really a big deal if String can be subclassed, as long as all methods in String are final, so that a subclass cannot mess with what a String is supposed to be like. If I'm accepting a String, and you give me a subclass of String, I don't care what you do in the subclass; as long as the content and the behavior of the String superclass is not tempered with.
Of course, being such a basic type, it's wise to mark String
as final to avoid all confusions.
In your use case, you can just have an abstract Employee, and make sure all subclasses are implemented as immutable. Any Employee object, at runtime, must belong to a concrete subclass, which is immutable.
If, you cannot control who subclasses Employee, and you suspect that they are either morons that don't know what they are doing, or villains that intend to cause troubles, you can at least protect the part in Employee so that subclasses cannot mess it up. For example, the name of an Employee is immutable no matter what subclass does -
final String name;
protected Employee(String name) { this.name = name; }
public final String getName(){ return name; } // final method!
However, such defensive design is often unjustified in most applications by most programmers. We don't have to be so paranoid. 99% of coding are cooperative. No big deal if someone really need to override something, let them. 99% of us are not writing some core APIs like String or Collection. A lot of Bloch's advices, unfortunately, are based on that kind of use cases. The advices are not really helpful to most programmers, especially new ones, churning out in-house apps.