When (Not) to Use Java Abstract Classes
Want to learn more about when and when not to use Java abstract classes? Check out this post to learn more about abstract classes.
Join the DZone community and get the full member experience.
Join For FreeAbstract classes are a core feature of many object-oriented languages, such as Java. Perhaps for that reason, they tend to be overused and, indeed, misused. In this article, we’ll use some examples of patterns and anti-patterns to illustrate when to use abstract methods, and when not to.
While this article presents the topic from a Java perspective, it is also relevant to most other object-oriented languages, even those without the concept of abstract classes. To that end, let’s quickly define abstract classes. If you already know what abstract classes are, feel free to skip the following section.
Defining Abstract Classes
Technically speaking, an abstract class is a class that cannot be directly instantiated. Instead, it is designed to be extended by concrete classes that can be instantiated. Abstract classes can— and typically do — define one or more abstract methods, which themselves do not contain a body. Instead, concrete subclasses are required to implement the abstract methods.
Let’s fabricate a quick example:
public abstract class Base {
public void doSomething() {
System.out.println("Doing something...")
}
public abstract void doSomethingElse();
}
Note that doSomething()
– a non-abstract method – has implemented a body, while doSomethingElse()
– an abstract method – has not. You cannot directly instantiate an instance of Base
. Try this and your compiler will complain:
Base b = new Base();
Instead, you need to subclass Base
, like so:
public class Sub extends Base {
public abstract void doSomethingElse() {
System.out.println("Doin' something else!");
}
}
Note the required implementation of the doSomethingElse()
method.
Not all OO languages have the concept of abstract classes. Of course, even in languages without such support, it’s possible to simply define a class whose purpose is to be subclassed, and define either empty methods, or methods that throw exceptions, as the “abstract” methods that subclasses override.
The Swiss Army Controller
Let’s examine a common abuse of abstract classes that I’ve come across frequently. I’ve been guilty of perpetuating it; you probably have, too. While this anti-pattern can appear nearly anywhere in a code base, I tend to see it quite often in Model-View-Controller (MVC) frameworks at the controller layer. For that reason, I’ve come to call it the Swiss Army Controller.
The anti-pattern is simple: a number of subclasses, related only by where they sit in the technology stack, extend from a common abstract base class. This abstract base class contains any number of shared “utility” methods. The subclasses call the utility methods from their own methods.
Swiss army controllers generally come into existence like this:
- Developers start building a web application, using an MVC framework such as Jersey.
- Since they are using an MVC framework, they back their first user-oriented webpage with an endpoint method inside a
UserController
class. - The developers create a second webpage and, therefore, add a new endpoint to the controller. One developer notices that both endpoints perform the same bit of logic — say, constructing a URL given a set of parameters — and moves that logic into a separate
constructUrl()
method withinUserController
. - The team begins work on product-oriented pages. The developers create a second controller,
ProductController
, so as to not cram all of the methods into a single class. - The developers recognize that the new controller might also need to use the
constructUrl()
method. At the same time, they realize hey! those two classes are controllers! and, therefore, must be naturally related. So, they create an abstractBaseController
class, move theconstructUrl()
into it, and addextends BaseController
to the class definition ofUserController
andProductController
. - This process repeats until
BaseController
has ten subclasses and 75 shared methods.
Now, there are a ton of useful methods for the concrete controllers to use, simply by calling them directly. So, what’s the problem?
The first problem is one of design. All of those different controllers are, in fact, unrelated to each other. They may live at the same layer of our stack and may perform a similar technical role, but as far as our application is concerned, they serve different purposes. Yet, we’ve now locked them to a fairly arbitrary object hierarchy.
The second is more practical. You’ll realize it the first time you need to use one of the 75 shared methods from somewhere other than a controller, and you find yourself instantiating a controller class to do so.
String url = new UserController().constructUrl(key, value);
You’ll have created a trove of useful methods, which now require a controller instance to access. Your first thought might be something like, hey, I can just make the method static in the controller, and use it like so:
String url = UserController.constructUrl(key, value);
That’s not much better, and actually, a little worse. Even if you’re not instantiating the controller, you’ve still tied the controller to your other classes. What if you need to use the method in your DAO layer? Your DAO layer should know nothing about your controllers. Worse, in introducing a bunch of static methods, you’ve made testing and mocking much more difficult.
It’s important to emphasize the interaction flow here. In this example, a call is made directly to one of the concrete subclasses’ methods. Then, at some point, this method calls in to one or more of the utility methods in the abstract base class.
In fact, in this example, there was never a need for an abstract base controller class. Each shared method should have either been moved to its appropriate service-layer classes (if it takes care of business logic) or to a utility classes (if it provides general, supplementary functionality). Of course, as mentioned above, the utility classes should still be instantiable, and not simply filled with static methods.
Now, there is a set of utility methods that is truly reusable by any class that might need them. Furthermore, we can break those methods into related groups. The above diagram depicts a class called UrlUtility
, which might contain only methods related to creating and parsing URLs. We might also create a class with methods related to string manipulation, another with methods related to our application’s current authenticated user, etc.
Note also that this approach also fits nicely with the composition over inheritance principal.
Inheritance and abstract classes are a powerful construct. As such, numerous examples abound of its misuse, the Swiss Army Controller being a common example. In fact, I’ve found that most typical uses of abstract classes can be considered anti-patterns, and there are a few good uses of abstract classes.
The Template Method
With that said, let’s then look at one of the best uses described by the template method design pattern. I’ve found the template method pattern to be one of the lesser known — but more useful — of the design patterns out there.
You can read about how the patterns works in numerous places. It was originally described in the Gang of Four Design Patterns book; many descriptions can now be found online. Let’s see how it relates to abstract classes and how it can be applied in the real world.
For consistency, I’ll describe another scenario that uses MVC controllers. In our example, we have an application for which there exist a few different types of users (for now, we’ll define two: employee and admin). When creating a new user of either type, there are minor differences depending on which type of user we are creating. For example, assigning roles needs to be handled differently. Other than that, the process is the same. Furthermore, while we don’t expect an explosion of new user types, we will from time to time be asked to support a new type of user.
In this case, we would want to start with an abstract base class for our controllers. Since the overall process of creating a new user is the same regardless of user type, we can define that process once in our base class. Any details that differ will be relegated to abstract methods that the concrete subclasses will implement:
public abstract class BaseUserController {
// ... variables, other methods, etc
@POST
@Path("/user")
public UserDto createUser(UserInfo userInfo) {
UserDto u = userMapper.map(userInfo);
u.setCreatedDate(Instant.now());
u.setValidationCode(validationUtil.generatedCode());
setRoles(u); // to be implemented in our subclasses
userDao.save(u);
mailerUtil.sendInitialEmail(u);
return u;
}
protected abstract void setRoles(UserDto u);
}
Then, we need simply to extend BaseUserController
once for each user type:
@Path("employee")
public class EmployeeUserController extends BaseUserController {
protected void setRoles(UserDto u) {
u.addRole(Role.employee);
}
}
@Path("admin")
public class AdminUserController extends BaseUserController {
protected void setRoles(UserDto u) {
u.addRole(Role.admin);
if (u.hasSuperUserAccess()) {
u.addRole(Role.superUser);
}
}
}
Any time we need to support a new user type, we simply create a new subclass of BaseUserController
and implement the setRoles()
method appropriately.
Let’s contrast the interaction here with the interaction we saw with the swiss army controller.
Using the template method approach, we see that the caller (in this case, the MVC framework itself–responding to a web request–is the caller) invokes a method in the abstract base class, rather than the concrete subclass. This is made clear in the fact that we have made the setRoles()
method, which is implemented in the subclasses, protected. In other words, the bulk of the work is defined once, in the abstract base class. Only for the parts of that work that need to be specialized do we create a concrete implementation.
A Rule of Thumb
I like to boil software engineering patterns to simple rules of thumb. Of course, there are exceptions to every rule. However, it’s helpful to be able to quickly gauge whether I’m moving in the right direction with a particular design.
It turns out that there's good rule of thumb when considering the use of an abstract class. Ask yourself: Will callers of your classes be invoking methods that are implemented in your abstract base class, or methods implemented in your concrete subclasses?
If it’s the former, you are intending to expose only methods implemented in your abstract class — odds are that you’ve created a good, maintainable set of classes.
If it’s the latter, callers will invoke methods implemented in your subclasses, which in turn will call methods in the abstract class. There’s a good chance that a swiss-army anti-patten is forming.
Hope this helps! Let us know what you think in the comments below.
Opinions expressed by DZone contributors are their own.
Comments