Favor Skeletal Implementation in Java
Got duplicate code? Learn how to get rid of it while making what's left work for you. A skeletal implementation gets your interface and Abstract class working together.
Join the DZone community and get the full member experience.
Join For FreeSkeletal implementation is a design by which we can use the benefits of the interface and abstract class together.
The Java Collection API has adopted this kind of design: AbstractSet, AbstractMap, etc. are examples of Skeletal interfaces. Also, Joshua Bloch mentions skeletal interfaces in his book “Effective Java.”
In this article, we will see how we can efficiently design our system so it can use the features of both interface and the Abstract class.
Let's try to understand it with a problem.
Suppose we want to create different types of vending machines. To get products from the machines, we need to start the vending machine, then choose the product, pay for it, then collect it.
After that, the vending machine should be stopped.
First Approach
We can create a vending machine interface then for different product types. So, to get started, we will create a concrete implementation for a vending machine.
Code
package com.example.skeletal;
public interface Ivending {
void start();
void chooseProduct();
void stop();
void process();
}
package com.example.skeletal;
public class CandyVending implements Ivending {
@Override
public void start() {
System.out.println("Start Vending machine");
}
@Override
public void chooseProduct() {
System.out.println("Produce diiferent candies");
System.out.println("Choose a type of candy");
System.out.println("pay for candy");
System.out.println("collect candy");
}
@Override
public void stop() {
System.out.println("Stop Vending machine");
}
@Override
public void process() {
start();
chooseProduct();
stop();
}
}
package com.example.skeletal;
public class DrinkVending implements Ivending {
@Override
public void start() {
System.out.println("Start Vending machine");
}
@Override
public void chooseProduct() {
System.out.println("Produce diiferent soft drinks");
System.out.println("Choose a type of soft drinks");
System.out.println("pay for drinks");
System.out.println("collect drinks");
}
@Override
public void stop() {
System.out.println("stop Vending machine");
}
@Override
public void process() {
start();
chooseProduct();
stop();
}
}
package com.example.skeletal;
public class VendingManager {
public static void main(String[] args) {
Ivending candy = new CandyVending();
Ivending drink = new DrinkVending();
candy.process();
drink.process();
}
}
/**
Output :
Start Vending machine
Produce diiferent candies
Choose a type of candy
pay for candy
collect candy
Stop Vending machine
*********************
Start Vending machine
Produce diiferent soft drinks
Choose a type of soft drinks
pay for drinks
collect drinks
stop Vending machine
**/
For simplicity, I did not divide each step as an individual method. In chooseProduct(), I merged some steps.
Although it looks goods, the above code has some problems, if we review it carefully, we can see that there is a lot of duplicate code. The start(), stop(), and process() methods do same thing in each concrete implementation.
Code duplication increases three times when the number of concrete implementation increases.
We can create a utility class and put common code into it, but that will break the single responsibility principal and can introduce the shotgun surgery code smell.
Disadvantage of Interface
As the interface is a contract and does not contain method body, each implementation has to fulfill the contract and provide an implementation of all methods. Some of the methods might duplicate across the concrete implementation.
Second Approach
We can overcome it through Abstract class.
Code
package com.example.skeletal;
public abstract class AbstractVending {
public void start()
{
System.out.println("Start Vending machine");
}
public abstract void chooseProduct();
public void stop()
{
System.out.println("Stop Vending machine");
}
public void process()
{
start();
chooseProduct();
stop();
}
}
package com.example.skeletal;
public class CandyVending extends AbstractVending {
@Override
public void chooseProduct() {
System.out.println("Produce diiferent candies");
System.out.println("Choose a type of candy");
System.out.println("pay for candy");
System.out.println("collect candy");
}
}
package com.example.skeletal;
public class DrinkVending extends AbstractVending {
@Override
public void chooseProduct() {
System.out.println("Produce diiferent soft drinks");
System.out.println("Choose a type of soft drinks");
System.out.println("pay for drinks");
System.out.println("collect drinks");
}
}
package com.example.skeletal;
public class VendingManager {
public static void main(String[] args) {
AbstractVending candy = new CandyVending();
AbstractVending drink = new DrinkVending();
candy.process();
System.out.println("*********************");
drink.process();
}
}
Here, I provide common code implementation into the abstract class. And CandyVending and DrinkVending extends AbstractVending. This implementation gets rid of duplicate code but adds a new problem.
As CandyVending and DrinkVending extend the Abstract class, we can't have provision to extends another class or does not support Multiple inheritances.
Say I want to add a VendingServicing class, which will clean and check the vending machine.
In this scenario, I can’t extend VendingServicing as I have already extended AbstractVending. One thing I can do is create a composition, but again, we have to pass VendingMachine into it, which will strongly couple VendingServicing and VendingMachine.
Disadvantage of Abstract Class
We can’t support multiple inheritances due to the diamond problem. It would be great if we could use the advantages of both interface and Abstract.
But wait, there is.
Abstract Interface or Skeletal Implementation
To achieve skeletal implementation:
Step 1: Create an interface.
Step 2: Create an Abstract class that implement that interface and provides the implementation of common methods.
Step 3: In the subclass, create a private inner class, which extends the Abstract class. Now this class can extend and implement any interfaces while using the common method by delegating calls to the Abstract class.
Code
package com.example.skeletal;
public interface Ivending {
void start();
void chooseProduct();
void stop();
void process();
}
package com.example.skeletal;
public class VendingService {
public void service()
{
System.out.println("Clean the vending machine");
}
}
package com.example.skeletal;
public abstract class AbstractVending implements Ivending {
public void start()
{
System.out.println("Start Vending machine");
}
public void stop()
{
System.out.println("Stop Vending machine");
}
public void process()
{
start();
chooseProduct();
stop();
}
}
package com.example.skeletal;
public class CandyVending implements Ivending {
private class AbstractVendingDelegator extends AbstractVending
{
@Override
public void chooseProduct() {
System.out.println("Produce diiferent candies");
System.out.println("Choose a type of candy");
System.out.println("pay for candy");
System.out.println("collect candy");
}
}
AbstractVendingDelegator delegator = new AbstractVendingDelegator();
@Override
public void start() {
delegator.start();
}
@Override
public void chooseProduct() {
delegator.chooseProduct();
}
@Override
public void stop() {
delegator.stop();
}
@Override
public void process() {
delegator.process();
}
}
package com.example.skeletal;
package com.example.skeletal;
public class DrinkVending extends VendingService implements Ivending {
private class AbstractVendingDelegator extends AbstractVending
{
@Override
public void chooseProduct() {
System.out.println("Produce diiferent soft drinks");
System.out.println("Choose a type of soft drinks");
System.out.println("pay for drinks");
System.out.println("collect drinks");
}
}
AbstractVendingDelegator delegator = new AbstractVendingDelegator();
@Override
public void start() {
delegator.start();
}
@Override
public void chooseProduct() {
delegator.chooseProduct();
}
@Override
public void stop() {
delegator.stop();
}
@Override
public void process() {
delegator.process();
}
}
package com.example.skeletal;
public class VendingManager {
public static void main(String[] args) {
Ivending candy = new CandyVending();
Ivending drink = new DrinkVending();
candy.process();
System.out.println("*********************");
drink.process();
if(drink instanceof VendingService)
{
VendingService vs = (VendingService)drink;
vs.service();
}
}
}
/**
Start Vending machine
Produce diiferent candies
Choose a type of candy
pay for candy
collect candy
Stop Vending machine
*********************
Start Vending machine
Produce diiferent soft drinks
Choose a type of soft drinks
pay for drinks
collect drinks
Stop Vending machine
Clean the vending machine
**/
Looking at the above design, I create an interface, then create an abstract class where I define all common implementations. Then, for each subclass, I implement a delegator class.
And using that delegator, we forward the call to AbstractVending.
Benefits of Skeletal Implementation
A subclass can extend other classes, like DrinkVending.
Get rid of duplicate code by delegating calls to the Abstract class.
If a subclass needs a new implementation of interface, it can do so.
Conclusion
When your interface has some common methods, always create an Abstract class. You can then use subclasses a delegator. Always try to use skeletal implementation.
Opinions expressed by DZone contributors are their own.
Comments