Benefits of using generics in a base class that also implement the same class

I recently came across this scenario in code that I did not write, and although there may be some advantage in terms of this approach, I cannot squeeze this rationale out of my own brain. So before I go and look stupid, I hope to get a response here.

The service interface looks something like this:

public interface Service {...} 

The base class then adds a generic reference to the service interface, where T extends the service, but then the generic base class also implements the interface. Something like that:

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

Why would you do this? I notice that in practice, the ServiceBase extension always uses the same class name as T as the one that is declared; therefore, there is no magical polymorphic benefit. Something like that:

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

and the MyService class is never a container for the generic type (for example, I don't think this signals some sort of self-consistent list where MyService can contain a list of MyServices).

Any ideas / thoughts on why anyone would do this?

+6
source share
3 answers

Why would you do this? I notice that in practice, the ServiceBase extension always uses the same class name as T is declared; so there really isn’t any magical polymorphic benefit here.

Generics do not exist to create magical polymorphism. This is mainly a way to add type constraints at compile time to reduce the awkward type and type of error at runtime.

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

Without using generics, we can declare such a method as 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, a concrete class can return any instance of ServiceBase.

For example, the next child class will compile because we are not forced to change the return 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 the base class:

ServiceBase with generics

 public abstract class ServiceBase<T extends Service> implements Service { public abstract T createService(); @Override public void process() { createService().process(); } } 

A particular class has no choice; it is forced to change the return type of createService() with the declared parameterized type.

MyService with generics

 public class MyService extends ServiceBase<MyService> { @Override public MyService createService() { return new MyService(); } } 
+4
source

I made an example using your class and interface declarations (except that I made a ServiceBase paragraph), which should illustrate the use of 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 that we have services managed by a ServiceManager that can set up a service environment so that they are ready to synchronize with each other. How the configure command is interpreted depends on the particular service. Therefore, the configure() declaration is in our interface.

ServiceBase handles the core synchronization stuff that should happen in the general case when two services want to synchronize. Separate implementations of ServiceBase should not deal with this.

However, ServiceBase does not know how a particular implementation itself synchronizes with a particular other service implementation. Therefore, he must delegate this part of the synchronization to his subclass.

Generics now come into play. ServiceBase also does not know what type of service it can synchronize to. He must also delegate this decision to his subclass. He can do this using the T extends Service construct

Given this structure, now imagine two specific subclasses of ServiceBase : ProducerService and ConsumerService ; Consumer services can only be synchronized with the manufacturer’s service and vice versa. Therefore, two classes indicate in their declaration ServiceBase<ConsumerService> respectively ServiceBase<ProducerService> .

Conclusion

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

+2
source

You have not placed any of the definitions of these classes that use a type parameter (which is likely to convey the rationale for this design or, possibly, its absence ...), but in all cases type is a way to parameterize the class, just like the method can be parameterized.

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

At the same time, ServiceBase takes a type argument, which is a subtype of Service . This tells us that the service implementation is likely to be “related” to another type of implementation (perhaps the same type as the current one). This relationship can be anything necessary for a particular design requirement, for example. the type of Service to which this implementation can delegate, the type of Service , which can call this service, etc.

How do I read the following announcement

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

approximately: ServiceBase is a basic implementation of a service that can have a statically typed connection with some other type of service.

These two aspects are completely independent.

0
source

Source: https://habr.com/ru/post/1013692/


All Articles