A well-designed module hides all of its implementation details, cleanly separating its API from its implementation.
Make each class or member as inaccessible as possible.
If a top-level class or interface can be made package-private, it should be. If a package-private top-level class or interface is used by only one class, consider making it a private nested class of the sole class that uses .it.
For fields, methods, nested classes and nested interfaces there are four possible access levels:
After designing the public API, try to make all other members private.
Instances fields should never be public. Classes with public mutable fields are not thread-safe.
A final field containing a reference to a mutable object has all the disadvantages of a nonfinal field.
It is wrong for a class to have a public static final array field, or an accesor that returns such a field.
You can't change the representation of public fields without changing the API, you can't enforce invariants, and you can't take auxiliary action when a field is accessed.
If a class is accessible outside its package, provide accessor methods to preserve the flexibility to change the class' internal representation.
An immutable class is a class whose instances cannot be modified. All of the information contained in each instance is provided when it is created and is fixed for the lifetime of the object.
To make a class immutable:
Immutable objects are inherently thread-safe; they require no synchronization.
The only real disadvantage of immutable classes is that they require a separate object fo each distinct value.
Make every field final unless there is a compelling reason to make it nonfinal.
It is safe to use inheritance within a package, where the subclass and the superclass implementations are under the control of the same programmers. But inheriting from ordinary concrete classes across package boundaries however, is dangerous.
Inheritance violates encapsulation, because it depends on the implementation of its superclass.
Instead of extending an existing class, give your new class a private field that references an instance of the existing class. Each instance method in the new class forwards to the corresponding method on the contained instance and returns the results.
Inheritance is appropriate only in circumstances where the subclass really is a subtype of the superclass.
The class must document precisely the effects of overriding any method. For each public or protected method or constructor, the documentation must indicate which overridable methods the method or constructor invokes, in what sequence, and how the results of each invocation affect subsequent processing.
A class may have to provide hooks into its internal workings in the form of judiciously chosen protected methods. But you should expose as few protected members as possible.
You must test your class by writing subclasses before you release it.
It is generally not a good idea for a class designed for inheritance to implement Cloneable or Serializable. If they do, neither clone nor readObject may invoke an overridable method.
The best solution is to prohibit subclassing in classes that are not designed and documented to be safely subclassed. You can do this by declaring the class final, or by making all constructors private and adding public static factories.
Move the body of each overridable method to a private helper method. Then replace each self-use of an overridable method with a direct invocation of the overridable method's private helper.
Existing classes can be easily retrofitted to implement a new interface. Existing classes cannot be retrofitted to extend a new abstract class.
Interfaces are ideal for defining mixins, to provide optional behavior. They also allow the construction of nonhierarchical type frameworks.
You can combine the virtues of interfaces and abstract classes by providing an abstract skeletal implementation class to go with each nontrivial interface that you export.
However it is far easier to evolve an abstract class than an interface. Once an interface is released and widely implemented, it is almost impossible to change.
When a class implements an interface, the interface serves as a type that can be used to refer to instances of the class.
The constant interface pattern is a poor use of interfaces.
Tagged classes jumble together multiple implementations in a single class. Instances are burdened with irrelevant fields belonging to other flavors. You can't add a flavor to a tagged class unless you can modify its source file, and you must remember to add a case to every switch statement, or the class will fail at runtime. Tagged classes are verbose, error-prone, and inefficient.
First define an abstract class containing an abstract method for each method in the tagged class whose behavior depends on the tag value.
Function pointers, delegates and lambda expressions are typically used to allow the caller of a function to specialize its behavior by passing in a second function.
On Java object references can be used to achieve a similar effect. It is possible to define an object whose methods perform operations on other objects, passed explicitly to the methods. An instance of a class that exports exactly one such method is effectively a pointer to that method. Such instances are known as function objects.
We need to define a strategy interface to go with the concrete strategy class.
A nested class should exist only to serve its enclosing class. There are four kinds of nested classes: static member classes, nonstatic member classes, anonymous classes, and local classes.
A static member class is an ordinary class that happens to be declared inside another class and has access to all of the enclosing class's members, even those declared private. A common use is a public helper class, useful only in conjunction with its outer class.
Each instance of a nonstatic member class is implicitly associated with an enclosing instance of its containing class. You can invoke methods on the enclosing instance or obtain a reference to enclosing instance using the qualified this construct. One common use is to define an Adapter that allows an instance of the outer class to be viewed as an instance of some unrelated class.
If you declare a member that does not require access to an enclosing instance, always put the static modifier in its declaration. If you omit this modifier, each instance will have an extraneous reference to its enclosing instance.
A common use of private static member classes is to represent components of the object represented by their enclosing class.
An anonymous class has no name. It is not a member of its enclosing class. It is simultaneously declared and instantiated at the point of use. Anonymous classes have enclosing instances if and only if they occur in a nonstatic context. But even if the occur in a static context, they cannot have any static members. Clients of anonymous class can't invoke any members, except those it inherits from its supertype. Common uses of anonymous classes are to create function objects on the fly, to create process objects, and to use them within static factory methods.
If a nested class needs to be visible outside of a single method or its too long to fit comfortably inside a method, use a member class. If each instance of the member class needs a reference to its enclosing instance, make it nonstatic; otherwise, make it static. Assuming the class belongs inside a method, if you need to create instances from only one location, and there is a preexisting type that characterizes the class, make it an anonymous class; otherwise, mae it a local class.
Add new comment