Should an abstract class implement interface java as opposed to defining its abstract methods?

476    Asked by MatsudaHara in Java , Asked on Oct 11, 2022

I'm defining a class structure for persisting to our cassandra database, and I'm unsure about using a combination of an abstract class and an interface. I have two concrete classes, one for persisting reports and the other for persisting configurations. They both have separate entity classes defined elsewhere that the entityMapper uses. I have read this post which is very similar, but I don't think their example really highlights what I want to know.

My structure looks like this:

Interface

public interface CassandraRepository {
   void save(T object);
    void delete(T object);
}
Abstract class
import com.datastax.driver.core.Session;
import com.datastax.driver.mapping.Mapper;
import com.datastax.driver.mapping.MappingManager;
abstract class AbstractGenericRepository implements CassandraRepository {
    private final Session sesion;
    Mapper entityMapper;
    AbstractGenericRepository(final Session session) {
        this.session = session;
        final MappingManager manager = new MappingManager(this.session);
        this.entityMapper = manager.mapper(getRepositoryClass());
    }
    protected abstract Class getRepositoryClass();
}
Configuration class
import com.model.Configuration;
import com.datastax.driver.core.Session;
public class ConfigurationRepository extends AbstractGenericRepository {
    public ConfigurationRepository(final Session session) {
        super(session);
    }
    @Override
    public void delete(final Configuration object) {
        this.entityMapper.delete(object);
    }
    @Override
    public void save(final Configuration object) {
        this.entityMapper.save(object);
    }
    @Override
    protected Class getRepositoryClass() {
        return Configuration.class;
    }
}
Report class
import com.model.Report;
import com.datastax.driver.core.Session;
public class ReportRepository  extends AbstractGenericRepository {
ReportRepository(Session session) {
        super(session);
    }
    @Override
    public void save(Report object) {
        this.entityMapper.save(object);
    }
    @Override
    public void delete(Report object) {
        this.entityMapper.delete(object);
    }
    @Override
    protected Class getRepositoryClass() {
        return Report.class;
    }
}

My question is: is there a point of having the interface at all? Could I simply define both the save and delete methods as abstract methods in the abstract class itself, and eliminate the interface.

I've also realised while typing this up that I could just move both the save and delete implementations into the abstract itself since they do the same thing. But in a case where they each implemented the methods differnetly, is there any benefit to having the interface at all?

Why could I not always use an abstract class over an interface, since it allows you to provide abstract methods that must be implemented in subclasses (like an interface), but then also allows you to define common code implementations between them (which interfaces do not allow)?

Answered by Matt Dyer

Regarding the abstract class implements interface Java -

First, let's rename the repository and interface, and then we can talk about why creating an additional layer of abstraction (the interface) is beneficial.

The CassandraRepository interface has a name problem. It has the word "Cassandra" in it, which ties it to a database vendor.

Let's rename this to simply Repository instead.

public interface Repository
{
    void save(T object);
    void delete(T object);
}
Now, the AbstractGenericRepository class has a naming problem too. This is where you should begin coupling your repository classes to a database vendor.
Let's rename this to CassandraRepository and have it implement the interface:
abstract class CassandraRepository implements Repository
{
    private final Session sesion;
    Mapper entityMapper;
    CassandraRepository(final Session session) {
        this.session = session;
        final MappingManager manager = new MappingManager(this.session);
        this.entityMapper = manager.mapper(getRepositoryClass());
    }
    protected abstract Class getRepositoryClass();
}
The ReportRepository class also has a naming problem. It is tied to a database vendor, yet you need specific methods for reports. Rename this class to CassandraReportRepository and have it implement a new interface: `ReportRepository':
public class CassandraReportRepository extends CassandraRepository implements ReportRepository
{
    ...
}
public interface ReportRepository extends Repository
{
    ReportResult run(Report reportToRun);
}
The CassandraReportRepository inherits from an abstract class coupled to a particular database vendor, and this is apparent through how these things are named.
This next bit is why you define an interface for an abstract class:
public class ReportService
{
    private final ReportRepository repository;
    public ReportService(ReportRepository repository) {
        this.repository = repository;
    }
    // Methods that use this.repository
}

The ReportService has a dependency on the ReportRepository interface, not the database vendor specific abstract class (or even the database vendor specific concrete class. This allows you to isolate this service class in a unit test, pass in a mock report repository, and test the behaviour of the service class by itself.

This is one of the main benefits of defining an interface for an abstract class. Loose coupling lends itself to easily testable code. If you decide to switch database vendors, then code should only need to be refactored within your data access layer.

You can define a new class called, say, MongoDbRepository. You would create a new class called MongoDbReportRepository that implements the ReportRepository interface. Since the ReportRepository interface did not change, any object that depends on this interface will also not need to change.



Your Answer

Interviews

Parent Categories