Sunday, April 17, 2011

Classes and Interfaces - Part 2

Design and document for inheritance
  1. Any class should document its public/protected/constructor methods indicating which overridable method it invokes and in what order and how each invocation affects the subsequent processing. Documenting the inner details is unavoidable if you expect a class to be inherited, as otherwise the inherited class might break the assumptions made by the base class.
  2. A class may have to provide hooks into its internal working in the form of judicially chosen protected methods, or in rare instances protected fields.
  3. While using inheritance, constructor should not use overridable methods directly or indirectly particularly in case the overridden method relies on the initialization of some subclass variables.
  4. Classes designed for inheritance should avoid implementing Cloneable and Serializable interfaces as it adds to the burden of those who subclass it. Also, as clone() and readObject() methods behave like constructor, they should also not use any overridable method directly or indirectly.
  5. If you implement Serializable in a possible base class, you should make readResolve and writeReplace methods protected rather than private as otherwise they will be ignored by the subclasses.
  6. If a class is not designed for subclassing, it is always best to prohibit it from being subclassed by either making the constructor private or declaring the class as final.
Prefer inheritance to abstract classes
  1. Existing classes can’t inherit abstract classes. Interfaces are ideal for defining mixins (mixin is a type that a class can implement in addition to its primary type to provide some optional behaviour). Interfaces can extend multiple interfaces. Interfaces enhance safe and powerful functionality enhancement via wrapper classes.
  2. You can provide an Abstract implementation of the Interface always so that the clients can choose to go with any one of them. An existing class can make use of both the Abstract implementation and interfaces as follows. Make the existing class implement the interface. Create a private inner class for the existing class which extends the abstract class. Let all the calls of the existing class forward the request to the inner class. This method is called simulated multiple-inheritance. A variation of this is simple implementation where you provide a concrete class rather than an abstract implementation and the client can override the needed methods. The basis is that it is far easier to evolve an abstract class than an interface.
Use interfaces only to define types
  1. The constant interface pattern (where an interface is used only to expose static final fields) is a poor use of interfaces. To export constants, always go for enums or non-instantiable utility classes. Note: You can always use static import facility to avoid qualifying a constant name with its class name.
Prefer class hierarchies to tagged classes
  1. Tagged classes are those which try to handle multiple cases in a single class. For example, consider a class called Shape which provides area, circumference etc for both circle and rectangle shapes by using a lot of if-else logics. If you want to add a new shape, the class will become messy.
Use function objects to represent strategies
  1. Strategies are usually stateless and hence can also be singletons.
Favour static member classes over nonstatic
  1. There are 4 kinds of nested classes: static member classes, nonstatic member classes, anonymous classes, and local classes.
  2. Static member classes have access to all the private members of the enclosing classes. These classes are like any other static member of the class. If it is declared private, it is accessible only within the enclosing class. Public static classes can be used as a public helper function useful only in conjunction with the enclosing class.
  3. Each instance of a non-static member class is implicitly associated with an instance of the enclosing class (accessed using this instance). It is impossible to create an instance of a non-static member class without an instance of the enclosing class. Common use of such classes is to implement adaptor classes. E.g. you can use non-static member classes to define iterators. These classes are commonly used to represent components of the object represented by its enclosing class.
  4. Anonymous classes have no names and can be declared anywhere an expression is allowed. They are simultaneously declared and instantiated at the point of use. They have enclosing instances if and only if they occur in a non-static context. They are mainly used to create function objects on the fly. E.g. When trying to sort an array, you can pass an anonymous class using an anonymous Comparable interface. They can also be used to create Process objects such as Runnable, Thread, Timertask etc. Static factory methods can use Anonymous classes to provide a different type of implementation of the interface.
  5. Local classes are least frequently used and can be declared anywhere a local variable can be declared. They can’t contain static members.
I have taken these points from the book "Effective Java" which I consider as a MUST READ book for every JAVA developer.

No comments:

Post a Comment