0

I'm new to generics and delving into things that are basically well beyond the scope of my knowledge so please bear with me on this. Perhaps what I'm asking is silly but I cannot seem to find a definitive answer on whether or not what I wish to do is possible, perhaps because I don't know the correct terminology to look for.

I have two Java classes which are parameterized with an entity of type T - a repository and a service. Since all my repositories and all my services are going to be doing some identical basic tasks, my generic repo and service have implemented said tasks and made them available through protected functions to anything that extends them. So far, everything is peachy. Currently they look something like this:

Repository class:

public interface GenericRepo<T> {
    protected T findObject();
}

Service class:

public class GenericService<T> {
    private GenericRepo<T> repo;

    public GenericService(GenericRepo<T> repo) {
        this.repo = repo;
    }

    protected T findObject() {
        return this.repo.findObject();
    }
}

The problem comes when I wish to extend my service and allow people to use the subclass of the repository. While many of the entities are basic and the functionality is covered with the generic functions, sometimes a special case is needed to perform an additional function on an entity. In the setup, the repo is implemented/extended and then that extended class is passed to the service (which accepts it because it is a type of GenericRepo<T>). The specific repo would look something like below:

public class ExtendedRepo implements GenericRepo<Entity> {
   protected Entity findObjectByWeirdKeyOnEntity(String weirdKey) {
       //awesome code that does stuff here
   }
}

I've also declared the service as generic because there are lots of possibilities for custom functionality on the service as well. So, the GenericService is also extended like below:

public class ExtendedService extends GenericService<Entity> {
    public ExtendedService(ExtendedRepo repo) {
        super(repo);
    }

    public Entity findObjectByWeirdKeyOnEntity() {
        // Do stuff to generate weird key to search by
        return this.repo.findObjectByWeirdKeyOnEntity(weirdKey);
    }
}

This allows me to do custom functions on the service but also utilize all basic functions on the generic service without need to replicate code. However, because the variable repo in GenericService is declared as type GenericRepo<T>, only those functions actually on type GenericRepo<T> are available to the generic service class and/or anything that extends it. Therefore despite the fact I'm passing ExtendedRepo to my ExtendedService whose super class has created and made available an instance of the GenericRepo class through the subclass of ExtendedRepo, I cannot use the declared variable on GenericService to call any of the functions on ExtendedRepo. The code in the code block immediately above fails - this.repo does not know about the function findObjectByWeirdKeyOnEntity because it's a variable of type GenericRepo<T>. Currently to use any custom function on the extended repository, I have done the following:

public class ExtendedService extends GenericService<Entity> {
    private ExtendedRepo extendedRepo;

    public ExtendedService(ExtendedRepo repo) {
        super(repo);
        this.extendedRepo = repo;
    }

    public Entity findObjectByWeirdKey() {
        return this.extendedRepo.findObjectByWeirdKeyOnEntity(weirdKey);
    }
}

Re-declaring and keeping a separate variable for the repository in the extended service seems wrong as I'm essentially keeping two instances of the same class just so I can use one custom function in the extended class while also using all common functionality of the super class. Is there any way to create the variable GenericRepo on GenericService to be of any type which extends GenericRepo that will allow the classes extending GenericService to use the custom methods on the extended version of the GenericRepo class?

Eyowzitgoin
  • 129
  • 2
  • 12
  • Interfaces cannot have the `protected` access specifier. You should get a red squiggly line error in your `GenericRepo` interface – Shankha057 Nov 16 '19 at 21:52
  • See [this](https://stackoverflow.com/questions/5376970/protected-in-interfaces) thread to know why. – Shankha057 Nov 16 '19 at 22:06
  • The repos are actually extending Spring JPA Repository Interfaces and giving it ```protected``` access works fine. I am able to access all the ```JpaRepository``` methods as well as my ```GenericRepo``` methods without issue. – Eyowzitgoin Nov 17 '19 at 02:02

2 Answers2

3

You can add one more type parameter to GenericService which will represent type of GenericRepo:

public class GenericService<R extends GenericRepo<T>, T> {
    protected R repo;

    public GenericService(R repo) {
        this.repo = repo;
    }

    protected T findObject() {
        return this.repo.findObject();
    }
}

public class ExtendedService extends GenericService<ExtendedRepo, Entity> {
    public ExtendedService(ExtendedRepo repo) {
        super(repo);
    }

    public Entity findObjectByWeirdKeyOnEntity(String weirdKey) {
        return this.repo.findObjectByWeirdKeyOnEntity(weirdKey);
    }
}

Note: in your last example you are keeping two references to the same instance, but not two instances of the same class.

IlyaMuravjov
  • 2,352
  • 1
  • 9
  • 27
0

GenericService has a repo of type GenericRepo. What you are doing in the constructor of ExtendedService will only be visible at runtime, i.e. the runtime type of the repo instance will be of type ExtendedService (provided it's in the ExtendedService objects only).
But at compile time, repo is of type GenericRepo and GenericRepo doesn't have the method findObjectByWeirdKeyOnEntity.
So you have to explicitly case it to ExtendedService if you want to use it in the ExtendedService class. So simply do this: return ((ExtendedRepo)this.repo).findObjectByWeirdKeyOnEntity(weirdKey);

Or

Use @Banannon's solution. It is also a good one.

Shankha057
  • 1,296
  • 1
  • 20
  • 38
  • I believe this would work but the idea is to abstract out the complexity so future developers do not need to know they need to cast. Therefore, I believe Bananon's answer will suit my purpose better. – Eyowzitgoin Nov 17 '19 at 02:07
  • @Eyowzitgoin it totally depends on your use case, if you have only a single method in a single class where you are having to do it, then casting is not a problem for anyone but if you have to cast in multiple places then Bannannon's solution is better – Shankha057 Nov 17 '19 at 02:15
  • 1
    It's actually code designed to sit on top of Spring REST controllers and Spring Data JPA for _anyone_ to use. The idea is to make setting up REST endpoints which can search/filter on any field and any combination of fields on the entity, including paging and sorting, via URL query strings. I hide the complexity of doing this in the generic service and JPA repo but must allow customized functions in both if needed/desired. You can see the actual code [here](https://github.com/jonflacke/hedgehog) if you'd like. – Eyowzitgoin Nov 17 '19 at 02:33