2

I'm currently working at a company that has a diverse set of modules. In that company if you want to provide module internals you provide it via a java interface, that hides the actual implementing type and gives an interface for the requesting module. Now I want to have one provider to be able to provide data for multiple modules that expose different fields or methods of the actual internal data.

Therefore I have an internal Object, which has some data and I have an interface for each module that needs access to some but not strictly all fields. Finally I have an external object that implements all those interfaces and holds an instance of the internal object to delegate the method calls:

public class InternalObject {
    public int getA() { return 0; }
    public int getB() { return 0; }
}

public interface ModuleXObject {
   int getA();
}

public interface ModuleYObject {
    int getA();
    int getB();
}

public class ExternalObject implements ModuleXObject, ModuleYObject {
    private InternalObject _internal;

    public int getA() { return _internal.getA(); }
    public int getB() { return _internal.getB(); }
}

Now that is all fine and dandy, but if I want to provide - lets say - repository methods for finding a list of said objects typed for the correct module, I run into problems with how I can achieve that. I would wish for something like the following:

public interface ModuleXObjectRepository {
    List<ModuleXObject> loadAllObjects();
}

public interface ModuleYObjectRepository {
    List<ModuleYObject> loadAllObjects();
}

public class ExternalObjectRepository implements ModuleXObjectRepository, ModuleYObjectRepository {
    public List<ExternalObject> loadAllObjects() {
        // ...
    }
}

This doesn't compile saying the return type is incompatible. So my question is, if it is possible to achieve something like that and if, how?


I should note that I tried some different approaches which I want to include for completeness and to portray their downsides (in my eyes).

Approach 1:

public interface ModuleXObjectRepository {
    List<? extends ModuleXObject> loadAllObjects();
}

public interface ModuleYObjectRepository {
    List<? extends ModuleYObject> loadAllObjects();
}

public class ExternalObjectRepository implements ModuleXObjectRepository, ModuleYObjectRepository {
    public List<ExternalObject> loadAllObjects() {
        // ...
    }
}

This approach is quite close to the solution I would prefer, but results in code like this:

List<? extends ModuleXObject> objects = repository.loadAllObjects();

Therefore requiring the user to include the "? extends" into each List-Declaration regarding to an invocation of loadAllObjects().

Approach 2:

public interface ModuleXObjectRepository {
    List<ModuleXObject> loadAllObjects();
}

public interface ModuleYObjectRepository {
    List<ModuleYObject> loadAllObjects();
}

public class ExternalObjectRepository implements ModuleXObjectRepository, ModuleYObjectRepository {
    public List loadAllObjects() {
        // ...
    }
}

This approach just omits the generic in the ExternalObjectRepository and therefore reduces the type safety too much in my opinion. Also I haven't tested if this actually works.


Just to reharse, is there any possible way to define the loadAllObjects-method in a way that enables users to get lists that are typed with the objects for their respective module without

  • requiring "? extends" in the users code
  • degrading type safety in the repository implementation
  • using class/interface level generics
  • Take a look at https://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java – Taylor Nov 15 '17 at 17:37
  • Hi Taylor and thank you for you comment! In fact I already thought about using "super" in some way but I couldn't come up which would be the exact solution. At least ` List loadAllObjects()` doesn't work. Do you have any better idea? – Lars Nielsen Nov 15 '17 at 17:44
  • I'll add my answer with details. Gimme a few – Taylor Nov 15 '17 at 17:45

1 Answers1

0

The challenge with allowing it to be typed as List<ModuleXObject> is that other code may hold is as a List<ExternalObject>.

All ExternalObject instances are ModuleXObject instances but the inverse is not true.

Consider the following additional class:

public class MonkeyWrench implements ModuleXObject{
    //STUFF
}

MonkeyWrench instances are NOT ExternalObject instances but if one could cast a List<ExternalObject> to a List<ModuleXObject> one could add MonkeyWrench instances to this collection, and this causes a risk of run time class cast exceptions and ruins type safety.

Other code could very easily have:

for(ExternalObject externalObject:externalObjectRepository.loadAllObjects())

If one of those instances is a MonkeyWrench instance, run time class cast, which is what generics are meant to avoid.

The implication of ? extends ModuleXObject is that you can read any object from the collection as a ModuleXObject but you can't add anything to the collection as other code may have additional constraints on the collection that are not obvious/available at compile time.

I'd suggest in your case to use ? extends ModuleXObject as its semantics seem to align with what you want, namely pulling out ModuleXObject instances, e.g.

ModuleXObjectRepository repo = //get repo however
for(ModuleXObject obj : repo.loadAllObjects()){
    //do stuff with obj
}
Taylor
  • 3,625
  • 1
  • 17
  • 31
  • Thank you again for your answer. So if I understand this correctly, there probably isn't a solution to the problem that satisfies all the constraints I requested. But what would the cleanest solution be? As I don't need to modify the loaded list (add to or remove from it) it is probably approach 1 with "? extends" in the users code. On the other hand I don't have other implementations of ModuleX/YObject but that is just how it currently is. Anyways abandoning type safety is probably the dirtier solution. – Lars Nielsen Nov 15 '17 at 18:07
  • I'd use the `? extends Whatever` approach. It's a pretty common pattern in libraries and has clear semantics. It's not uncommon for library consumers to deal with, at least in my experience. – Taylor Nov 15 '17 at 18:09