Homework Design Class Hierarchy Interface

For the love of all that is dear to software development avoid parallel but separate class hierarchies.

Today we’re going to talk a little bit about type hierarchy design and why composition is useful to consider in certain scenarios.

Let’s talk about a use of Java interfaces and interface implementers I encountered recently.

The Apache Storm project implements a near real time message processing system. The project contains two type hierarchies for sending messages. Ostensibly the two type hierarchies are separate because one serves the needs of simple clients that do common operations and the other serves the needs of advance clients that do uncommon operations. This was interesting to me because it is the type of scenario where direct inheritance is often used.

In pictures:

On the left we have how Storm is currently implemented. Circled on the right we have another way of implementing basic and advanced client use scenarios. I am, admittedly, over simplify the Storm project’s “Collector” types here. The project instead of using inheritance has two separate Java package trees and type hierarchies for these different usages. The interesting thing about this though is that the two hierarchies are designed to provide the same fundamental functionality, an API for sending events. The two cannot be used together, however, because they sit in different type hierarchies.

There is, for example, no parent “Collector” type which can be handed to another type for use in sending events. For the sake of the argument let’s look at an example use case, an error handler.

Here is a simplistic logic tree for some component in storm that may emit events using one of the collectors’ interfaces. The error case is the interesting one since it’s often a scenario where we want to execute the same logic regardless of the contents of “thing” or “data”. We can’t though. In Storm a bolt, the “thing” in the diagram, may emit using two different interfaces which don’t share a type hierarchy. There are ways around using separate but not related types, method overloading is one path.

The solution works, it even seems somewhat elegant at first glance. My issue with it is that were the collectors to share a common hierarchy or be built out of a composition of parts we would not need this solution in the first place. The output collectors incidentally also suffer from the 1-1 interface type to class type coding pattern that has been discussed here before. The proliferation of interfaces used only in one location add to set of incompatible but related types.

An alternative?

I do not mean to be overly critical of Apache Storm, the project is actually a very cool piece of technology. It just happens to be a public example of a design that I’ve seen in several places. Having said all that, let’s look at what could be done differently.

Composition over inheritance is another approach to assembling logic across types. The Go programming language is one of the better examples of having composition baked into the language. The idea is that instead of building out fixed hierarchies of types we instead define the specific behavioral goals (the interface contract) first and then implement the behavior as needed in one or more base classes. In composition our interfaces remain small and focused on similarly related behaviors in order to promote reuse and flexibility.

Java’s support for compositional style design is more lacking than Go’s, but we can still show what this looks like based on our mini Storm example from above.

Behaviors are now collected as independent interfaces which can be implemented separately. We can now have an output class that reports errors, like the one in the example above, or we can chose to forgo that behavior. Our error handling example from above is now also resolved since the behavior of emitting data is collected in a single interface. Finally, there is only a single implementation of Error and it is composed into the Outputter class via a forwarding method. As mentioned earlier while implementation composition is relatively effortless in Go, it is somewhat awkward in Java due to lack of language support.

Awkwardness aside, composing implementations gives us more freedom to write other types that use these classes. I hope this brief article encourages the evaluation of an additional OOP technique in future projects.


This article is part of our Academy Course titled Advanced Java.

This course is designed to help you make the most effective use of Java. It discusses advanced topics, including object creation, concurrency, serialization, reflection and many more. It will guide you through your journey to Java mastery! Check it out here!

1. Introduction

Whatever programming language you are using (and Java is not an exception here), following good design principles is a key factor to write clean, understandable, testable code and deliver long-living, easy to maintain solutions. In this part of the tutorial we are going to discuss the foundational building blocks which the Java language provides and introduce a couple of design principles, aiming to help you to make better design decisions.

More precisely, we are going to discuss interfaces and interfaces with default methods (new feature of Java 8), abstract and finalclasses, immutable classes, inheritance, composition and revisit a bit the visibility (or accessibility) rules we have briefly touched in part 1 of the tutorial, How to create and destroy objects.

2. Interfaces

In object-oriented programming, the concept of interfaces forms the basics of contract-driven (or contract-based) development. In a nutshell, interfaces define the set of methods (contract) and every class which claims to support this particular interface must provide the implementation of those methods: a pretty simple, but powerful idea.

Many programming languages do have interfaces in one form or another, but Java particularly provides language support for that. Let take a look on a simple interface definition in Java.

package com.javacodegeeks.advanced.design; public interface SimpleInterface { void performAction(); }

In the code snippet above, the interface which we named declares just one method with name . The principal differences of interfaces in respect to classes is that interfaces outline what the contact is (declare methods), but do not provide their implementations.

However, interfaces in Java can be more complicated than that: they can include nested interfaces, classes, enumerations, annotations (enumerations and annotations will be covered in details in part 5 of the tutorial, How and when to use Enums and Annotations) and constants. For example:

package com.javacodegeeks.advanced.design; public interface InterfaceWithDefinitions { String CONSTANT = "CONSTANT"; enum InnerEnum { E1, E2; } class InnerClass { } interface InnerInterface { void performInnerAction(); } void performAction(); }

With this more complicated example, there are a couple of constraints which interfaces implicitly impose with respect to the nested constructs and method declarations, and Java compiler enforces that. First and foremost, even if it is not being said explicitly, every declaration in the interface is public (and can be only public, for more details about visibility and accessibility rules, please refer to section Visibility). As such, the following method declarations are equivalent:

public void performAction(); void performAction();

Worth to mention that every single method in the interface is implicitly declared as abstract and even these method declarations are equivalent:

public abstract void performAction(); public void performAction(); void performAction();

As for the constant field declarations, additionally to being , they are implicitly and so the following declarations are also equivalent:

String CONSTANT = "CONSTANT"; public static final String CONSTANT = "CONSTANT";

And finally, the nested classes, interfaces or enumerations, additionally to being , are implicitly declared as . For example, those class declarations are equivalent as well:

class InnerClass { } static class InnerClass { }

Which style you are going to choose is a personal preference, however knowledge of those simple qualities of interfaces could save you from unnecessary typing.

3. Marker Interfaces

Marker interfaces are a special kind of interfaces which have no methods or other nested constructs defined. We have already seen one example of the marker interface in part 2 of the tutorial Using methods common to all objects, the interface . Here is how it is defined in the Java library:

public interface Cloneable { }

Marker interfaces are not contracts per se but somewhat useful technique to “attach” or “tie” some particular trait to the class. For example, with respect to , the class is marked as being available for cloning however the way it should or could be done is not a part of the interface. Another very well-known and widely used example of marker interface is :

public interface Serializable { }

This interface marks the class as being available for serialization and deserialization, and again, it does not specify the way it could or should be done.

The marker interfaces have their place in object-oriented design, although they do not satisfy the main purpose of interface to be a contract.

4. Functional interfaces, default and static methods

With the release of Java 8, interfaces have obtained new very interesting capabilities: static methods, default methods and automatic conversion from lambdas (functional interfaces).

In section Interfaces we have emphasized on the fact that interfaces in Java can only declare methods but are not allowed to provide their implementations. With default methods it is not true anymore: an interface can mark a method with the keyword and provide the implementation for it. For example:

package com.javacodegeeks.advanced.design; public interface InterfaceWithDefaultMethods { void performAction(); default void performDefaulAction() { // Implementation here } }

Being an instance level, defaults methods could be overridden by each interface implementer, but from now, interfaces may also include methods, for example:

package com.javacodegeeks.advanced.design; public interface InterfaceWithDefaultMethods { static void createAction() { // Implementation here } }

One may say that providing an implementation in the interface defeats the whole purpose of contract-based development, but there are many reasons why these features were introduced into the Java language and no matter how useful or confusing they are, they are there for you to use.

The functional interfaces are a different story and they are proven to be very helpful add-on to the language. Basically, the functional interface is the interface with just a single abstract method declared in it. The interface from Java standard library is a good example of this concept:

@FunctionalInterface public interface Runnable { void run(); }

The Java compiler treats functional interfaces differently and is able to convert the lambda function into the functional interface implementation where it makes sense. Let us take a look on following function definition:

public void runMe( final Runnable r ) { r.run(); }

To invoke this function in Java 7 and below, the implementation of the interface should be provided (for example using Anonymous classes), but in Java 8 it is enough to pass method implementation using lambda syntax:

runMe( () -> System.out.println( "Run!" ) );

Additionally, the annotation (annotations will be covered in details in part 5 of the tutorial, How and when to use Enums and Annotations) hints the compiler to verify that the interface contains only one abstract method so any changes introduced to the interface in the future will not break this assumption.

5. Abstract classes

Another interesting concept supported by Java language is the notion of abstract classes. Abstract classes are somewhat similar to the interfaces in Java 7 and very close to interfaces with default methods in Java 8. By contrast to regular classes, abstract classes cannot be instantiated but could be subclassed (please refer to the section Inheritance for more details). More importantly, abstract classes may contain abstract methods: the special kind of methods without implementations, much like interfaces do. For example:

package com.javacodegeeks.advanced.design; public abstract class SimpleAbstractClass { public void performAction() { // Implementation here } public abstract void performAnotherAction(); }

In this example, the class is declared as and has one method declaration as well. Abstract classes are very useful when most or even some part of implementation details could be shared by many subclasses. However, they still leave the door open and allow customizing the intrinsic behavior of each subclass by means of abstract methods.

One thing to mention, in contrast to interfaces which can contain only declarations, abstract classes may use the full power of accessibility rules to control abstract methods visibility (please refer to the sections Visibility and Inheritance for more details).

6. Immutable classes

Immutability is becoming more and more important in the software development nowadays. The rise of multi-core systems has raised a lot of concerns related to data sharing and concurrency (in the part 9, Concurrency best practices, we are going to discuss in details those topics). But the one thing definitely emerged: less (or even absence of) mutable state leads to better scalability and simpler reasoning about the systems.

Unfortunately, the Java language does not provide strong support for class immutability. However using a combination of techniques it is possible to design classes which are immutable. First and foremost, all fields of the class should be . It is a good start but does not guarantee immutability alone.

package com.javacodegeeks.advanced.design; import java.util.Collection; public class ImmutableClass { private final long id; private final String[] arrayOfStrings; private final Collection< String > collectionOfString; }

Secondly, follow the proper initialization: if the field is the reference to a collection or an array, do not assign those fields directly from constructor arguments, make the copies instead. It will guarantee that state of the collection or array will not be changed from outside.

public ImmutableClass( final long id, final String[] arrayOfStrings, final Collection< String > collectionOfString) { this.id = id; this.arrayOfStrings = Arrays.copyOf( arrayOfStrings, arrayOfStrings.length ); this.collectionOfString = new ArrayList<>( collectionOfString ); }

And lastly, provide the proper accessors (getters). For the collection, the immutable view should be exposed using wrappers.

public Collection<String> getCollectionOfString() { return Collections.unmodifiableCollection( collectionOfString ); }

With arrays, the only way to ensure true immutability is to provide a copy instead of returning reference to the array. That might not be acceptable from a practical standpoint as it hugely depends on array size and may put a lot of pressure on garbage collector.

public String[] getArrayOfStrings() { return Arrays.copyOf( arrayOfStrings, arrayOfStrings.length ); }

Even this small example gives a good idea that immutability is not a first class citizen in Java yet. Things can get really complicated if an immutable class has fields referencing another class instances. Those classes should also be immutable however there is no simple way to enforce that.

There are a couple of great Java source code analyzers like FindBugs) and PMD) which may help a lot by inspecting your code and pointing to the common Java programming flaws. Those tools are great friends of any Java developer.

7. Anonymous classes

In the pre-Java 8 era, anonymous classes were the only way to provide in-place class definitions and immediate instantiations. The purpose of the anonymous classes was to reduce boilerplate and provide a concise and easy way to represent classes as expressions. Let us take a look on the typical old-fashioned way to spawn new thread in Java:

package com.javacodegeeks.advanced.design; public class AnonymousClass { public static void main( String[] args ) { new Thread( // Example of creating anonymous class which implements // Runnable interface new Runnable() { @Override public void run() { // Implementation here } } ).start(); } }

In this example, the implementation of the interface is provided in place as anonymous class. Although there are some limitations associated with anonymous classes, the fundamental disadvantages of their usage are a quite verbose syntax constructs which Java imposes as a language. Even the simplest anonymous class which does nothing requires at least 5 lines of code to be written every time.

new Runnable() { @Override public void run() { } }

Luckily, with Java 8, lambdas and functional interfaces all this boilerplate is about to gone away, finally making the Java code to look truly concise.

package com.javacodegeeks.advanced.design; public class AnonymousClass { public static void main( String[] args ) { new Thread( () -> { /* Implementation here */ } ).start(); } }

8. Visibility

We have already talked a bit about Java visibility and accessibility rules in part 1 of the tutorial, How to design Classes and Interfaces. In this part we are going to get back to this subject again but in the context of subclassing.

ModifierPackageSubclassEveryone Else
protectedaccessibleaccessiblenot accessible
<no modifier>accessiblenot accessiblenot accessible
privatenot accessiblenot accessiblenot accessible

Table 1

Different visibility levels allow or disallow the classes to see other classes or interfaces (for example, if they are in different packages or nested in one another) or subclasses to see and access methods, constructors and fields of their parents.

In next section, Inheritance, we are going to see that in action.

9. Inheritance

Inheritance is one of the key concepts of object-oriented programming, serving as a basis of building class relationships. Combined together with visibility and accessibility rules, inheritance allows designing extensible and maintainable class hierarchies.

Conceptually, inheritance in Java is implemented using subclassing and the keyword, followed by the parent class. The subclass inherits all of the public and protected members of its parent class. Additionally, a subclass inherits the package-private members of the parent class if both reside in the same package. Having said that, it is very important no matter what you are trying to design, to keep the minimal set of the methods which class exposes publicly or to its subclasses. For example, let us take a look on a class and its subclass to demonstrate different visibility levels and their effect:

package com.javacodegeeks.advanced.design; public class Parent { // Everyone can see it public static final String CONSTANT = "Constant"; // No one can access it private String privateField; // Only subclasses can access it protected String protectedField; // No one can see it private class PrivateClass { } // Only visible to subclasses protected interface ProtectedInterface { } // Everyone can call it public void publicAction() { } // Only subclass can call it protected void protectedAction() { } // No one can call it private void privateAction() { } // Only subclasses in the same package can call it void packageAction() { } } package com.javacodegeeks.advanced.design; // Resides in the same package as parent class public class Child extends Parent implements Parent.ProtectedInterface { @Override protected void protectedAction() { // Calls parent's method implementation super.protectedAction(); } @Override void packageAction() { // Do nothing, no call to parent's method implementation } public void childAction() { this.protectedField = "value"; } }

Inheritance is a very large topic by itself, with a lot of subtle details specific to Java. However, there are a couple of easy to follow rules which could help a lot to keep your class hierarchies concise. In Java, every subclass may override any inherited method of its parent unless it was declared as (please refer to the section Final classes and methods).

However, there is no special syntax or keyword to mark the method as being overridden which may cause a lot of confusion. That is why the annotation has been introduced: whenever your intention is to override the inherited method, please always use the annotation to indicate that.

Another dilemma Java developers are often facing in design is building class hierarchies (with concrete or abstract classes) versus interface implementations. It is strongly advised to prefer interfaces to classes or abstract classes whenever possible. Interfaces are much more lightweight, easier to test (using mocks) and maintain, plus they minimize the side effects of implementation changes. Many advanced programming techniques like creating class proxies in standard Java library heavily rely on interfaces.

10. Multiple inheritance

In contrast to C++ and some other languages, Java does not support multiple inheritance: in Java every class has exactly one direct parent (with class being on top of the hierarchy as we have already known from part 2 of the tutorial, Using methods common to all objects). However, the class may implement multiple interfaces and as such, stacking interfaces is the only way to achieve (or mimic) multiple inheritance in Java.

package com.javacodegeeks.advanced.design; public class MultipleInterfaces implements Runnable, AutoCloseable { @Override public void run() { // Some implementation here } @Override public void close() throws Exception { // Some implementation here } }

Implementation of multiple interfaces is in fact quite powerful, but often the need to reuse an implementation leads to deep class hierarchies as a way to overcome the absence of multiple inheritance support in Java.

public class A implements Runnable { @Override public void run() { // Some implementation here } } // Class B wants to inherit the implementation of run() method from class A. public class B extends A implements AutoCloseable { @Override public void close() throws Exception { // Some implementation here } } // Class C wants to inherit the implementation of run() method from class A // and the implementation of close() method from class B. public class C extends B implements Readable { @Override public int read(java.nio.CharBuffer cb) throws IOException { // Some implementation here } }

And so on… The recent Java 8 release somewhat addressed the problem with the introduction of default methods. Because of default methods, interfaces actually have started to provide not only contract but also implementation. Consequently, the classes which implement those interfaces are automatically inheriting these implemented methods as well. For example:

package com.javacodegeeks.advanced.design; public interface DefaultMethods extends Runnable, AutoCloseable { @Override default void run() { // Some implementation here } @Override default void close() throws Exception { // Some implementation here } } // Class C inherits the implementation of run() and close() methods from the // DefaultMethods interface. public class C implements DefaultMethods, Readable { @Override public int read(java.nio.CharBuffer cb) throws IOException { // Some implementation here } }

Be aware that multiple inheritance is a powerful, but at the same time a dangerous tool to use. The well known “Diamond of Death” problem is often cited as the fundamental flaw of multiple inheritance implementations, so developers are urged to design class hierarchies very carefully. Unfortunately, the Java 8 interfaces with default methods are becoming the victims of those flaws as well.

interface A { default void performAction() { } } interface B extends A { @Override default void performAction() { } } interface C extends A { @Override default void performAction() { } }

For example, the following code snippet fails to compile:

// E is not compilable unless it overrides performAction() as well interface E extends B, C { }

At this point it is fair to say that Java as a language always tried to escape the corner cases of object-oriented programming, but as the language evolves, some of those cases are started to pop up.

11. Inheritance and composition

Fortunately, inheritance is not the only way to design your classes. Another alternative, which many developers consider being better than inheritance, is composition. The idea is very simple: instead of building class hierarchies, the classes should be composed from other classes.

Let us take a look on this example:

public class Vehicle { private Engine engine; private Wheels[] wheels; // ... }

The class is composed out of and (plus many other parts which are left aside for simplicity). However, one may say that class is also an engine and so could be designed using the inheritance.

public class Vehicle extends Engine { private Wheels[] wheels; // ... }

Which design decision is right? The general guidelines are known as IS-A and HAS-A principles. IS-A is the inheritance relationship: the subclass also satisfies the parent class specification and a such IS-A variation of parent class. Consequently, HAS-A is the composition relationship: the class owns (or HAS-A) the objects which belong to it. In most cases, the HAS-A principle works better then IS-A for couple of reasons:

  • The design is more flexible in a way it could be changed
  • The model is more stable as changes are not propagating through class hierarchies
  • The class and its composites are loosely coupled compared to inheritance which tightly couples parent and its subclasses
  • The reasoning about class is simpler as all its dependencies are included in it, in one place

However, the inheritance has its own place, solves real design issues in different way and should not be neglected. Please keep those two alternatives in mind while designing your object-oriented models.

12. Encapsulation

The concept of encapsulation in object-oriented programming is all about hiding the implementation details (like state, internal methods, etc.) from the outside world. The benefits of encapsulation are maintainability and ease of change. The less intrinsic details classes expose, the more control the developers have over changing their internal implementation, without the fear to break the existing code (a real problem if you are developing a library or framework used by many people).

Encapsulation in Java is achieved using visibility and accessibility rules. It is considered a best practice in Java to never expose the fields directly, only by means of getters and setters (if the field is not declared as ). For example:

package com.javacodegeeks.advanced.design; public class Encapsulation { private final String email; private String address; public Encapsulation( final String email ) { this.email = email; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getEmail() { return email; } }

This example resembles what is being called in Java language: the regular Java classes written by following the set of conventions, one of those being allow the access to fields using getter and setter methods only.

As we already emphasized in the Inheritance section, please always try to keep the class public contract minimal, following the encapsulation principle. Whatever should not be , should be instead (or / , depending on the problem you are solving). In long run it will pay off, giving you the freedom to evolve your design without introducing breaking changes (or at least minimize them).

13. Final classes and methods

In Java, there is a way to prevent the class to be subclassed by any other class: it should be declared as .

package com.javacodegeeks.advanced.design; public final class FinalClass { }

The same keyword in the method declaration prevents the method in question to be overridden in subclasses.

package com.javacodegeeks.advanced.design; public class FinalMethod { public final void performAction() { } }

There are no general rules to decide if class or method should be or not. Final classes and methods limit the extensibility and it is very hard to think ahead if the class should or should not be subclassed, or method should or should not be overridden. This is particularly important to library developers as the design decisions like that could significantly limit the applicability of the library.

Java standard library has some examples of classes, with most known being class. On an early stage, the decision has been taken to proactively prevent any developer’s attempts to come up with own, “better” string implementations.

14. What’s next

In this part of the tutorial we have looked at object-oriented design concepts in Java. We also briefly walked through contract-based development, touched some functional concepts and saw how the language evolved over time. In next part of the tutorial we are going to meet generics and how they are changing the way we approach type-safe programming.

15. Download the Source Code

This was a lesson on How to design Classes and Interfaces.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

and many more ....

Email address:

Sign up


Andrey Redko

Categories: 1

0 Replies to “Homework Design Class Hierarchy Interface”

Leave a comment

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *