Question: Benefits of Using Generics in a Base Class that Also Implement the Same Class

Question

Benefits of Using Generics in a Base Class that Also Implement the Same Class

Answers 3
Added at 2017-01-01 22:01
Tags
Question

I recently ran across this scenario in code that I didn't write and while there may be some design benefit to this approach, I can't seem to squeeze this rationale out of my own brain. So before I go and look foolish, I'm hoping for some feedback here.

Service interface something like this:

public interface Service {...}

Then, a base class that adds a generic reference to the Service interface where T extends the Service, but then the overall base class also implements the interface. Something like this:

public class ServiceBase<T extends Service> implements Service {...}

Why would you do this? I'm noticing that in practice the extension of ServiceBase always uses the same class name as T as the one that is being declared; so there's not really any magic polymorphic benefit here. Something like this:

public class MyService extends ServiceBase<MyService> {...}

and, the MyService class is never a container for the generic (e.g., I don't believe this is signaling some kind of self-containing list, where MyService could contain a list of MyServices).

Any ideas/thoughts on why someone would do this?

Answers to

Benefits of Using Generics in a Base Class that Also Implement the Same Class

nr: #1 dodano: 2017-01-01 23:01

You haven't posted any of the definitions of these classes where the type parameter is used (which would most likely convey the rationale behind this design, or maybe the lack of it...), but in all cases, a type parameter is a way of parameterizing a class, just like a method can be parameterized.

The ServiceBase class implements a Service. This tells us that it implements the contract (methods) of a Service (to be more precise, subclasses of it can act as the implementation).

At the same time, ServiceBase takes a type argument that is a subtype of Service. This tells us that a service implementation probably has a "relationship" with another implementation type (possibly the same type as the current one). This relationship could be anything needed by the specific design requirement, e.g. the type of Service that this implementation can delegate to, the type of Service that can call this service, etc.

The way I read the following declaration

public class ServiceBase<T extends Service> implements Service {...}

is roughly: ServiceBase is a base implementation of a service, which can have a statically typed relationship with some other type of service.

These two aspects are completely independent.

nr: #2 dodano: 2017-01-01 23:01

Why would you do this? I'm noticing that in practice the extension of ServiceBase always uses the same class name as T as the one that is being declared; so there's not really any magic polymorphic benefit here.

Generics don't exist to create magic polymorphim. It is mainly a way to add constraints on types at compile time in order to reduce clumsy cast and error type at runtime.

In your case, suppose that ServiceBase class is abstract and has a process() method which needs to create at each call a new instance of the concrete class we declare in the parameterized type.
We call this abstract method createService().

Without using generics, we could declare the method like that public abstract ServiceBase createService().

ServiceBase without generics

public abstract class ServiceBase implements Service {

    public abstract ServiceBase createService();

    @Override
    public void process() {
         createService().process();
    }

}

With this declaration, the concrete class may return any instance of ServiceBase.

For example, the following child class will compile because we are not forced to change the returned type of createService() to the current declared type.

MyService without generics

public class MyService extends ServiceBase {

    @Override
    public ServiceBase createService() {    
        return new AnotherService();
    }

}

But if I use generics in base class :

ServiceBase with generics

public abstract class ServiceBase<T extends Service> implements Service {

    public abstract T createService();

    @Override
    public void process() {
         createService().process();
    }

}

The concrete class has no choice, it is forced to change the returned type of createService() with the parameterized type declared.

MyService with generics

public class MyService extends ServiceBase<MyService> {

    @Override
    public MyService createService() {  
        return new MyService();
    }

}
nr: #3 dodano: 2017-01-02 00:01

I made up an example using your class and interface declarations (except that I made ServiceBase abstract) which should illustrate the use of the generic types:

public interface Service {
    int configure(String cmd);
}

public abstract class ServiceBase<T extends Service> implements Service {
    private ServiceManager manager;     

    public ServiceBase(ServiceManager manager){
        this.manager = manager;
    }

    public final void synchronize(T otherService){
        manager.configureEnvironment(otherService.configure("syncDest"), configure("syncSrc"));
        synchronizeTo(otherService);
    }

    protected abstract void synchronizeTo(T otherService);
}

public class ProducerService extends ServiceBase<ConsumerService> {

    public ProducerService(ServiceManager manager) {
        super(manager);
    }

    @Override
    protected void synchronizeTo(ConsumerService otherService) { 
        /* specific code for synchronization with consumer service*/ 
    }           

    @Override
    public int configure(String cmd) { ... }
}

public class ConsumerService extends ServiceBase<ProducerService> {

    public ConsumerService(ServiceManager manager) {
        super(manager);
    }

    @Override
    protected void synchronizeTo(ProducerService otherService) {
        /* specific code for synchronization with producer service */           
    }

    @Override
    public int configure(String cmd) { ... }

}

Imagine we have services managed by a ServiceManager which can configure the environment of the services so that they are ready for synchronization with each other. How a configure command is interpreted is up to the specific service. Therefore a configure() declaration resides in our interface.

The ServiceBase handles the basic synchronization stuff that has to happen generally when two services want to synchronize. The individual implementations of ServiceBase shouldn't have to deal with this.

However ServiceBase doesn't know how a specific implementation of itself synchronizes to a specific other implementation of service. Therefore it has to delegate this part of synchronization to its subclass.

Now generics come into the play. ServiceBase also doesn't know to which type of service it is able to synchronize to. He has also to delegate this decision to its subclass. He can do this using the construct T extends Service

Given this structure now imagine two concrete subclasses of ServiceBase: ProducerService and ConsumerService; The consumer service can only synchronize to the producer service and the other way around. Therefore the two classes specify in their declaration ServiceBase<ConsumerService> respectively ServiceBase<ProducerService>.

Conclusion

Just like abstract methods can be used by superclasses to delegate the implementation of functionality to their subclasses, generic type parameters can be used by superclasses to delegate the "implementation" of type placeholders to their subclasses.

Source Show
◀ Wstecz