Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Favor Skeletal Implementation in Java

DZone's Guide to

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.

· Java Zone
Free Resource

Try Okta to add social login, MFA, and OpenID Connect support to your Java app in minutes. Create a free developer account today and never build auth again.

Skeletal 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

  1. A subclass can extend other classes, like DrinkVending.

  2. Get rid of duplicate code by delegating calls to the Abstract class.

  3. 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.

Build and launch faster with Okta’s user management API. Register today for the free forever developer edition!

Topics:
design decision ,designpattern ,interface ,abstract class ,java 5

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}