1

A real (but simplified) example from the program that I'm currently working on.

We have an abstract Request class and two classes that extend it: FooRequest and BarRequest. They both have dao's used to get instances of the two requests from the databases: FooRequestDao and BarRequestDao. All across the code base currently, if we want to get a list of Requests that fit some business logic criteria, we do the following:

List<OtherType> listOfOtherType= new ArrayList<>();

List<OtherType> listOfFooRequests = requestService.transformToOther(fooRequestDao.findAllFooRequestWithXYZ());

List<OtherType> listOfBarRequests = requestService.transformToOther(barRequestDao.findAllBarRequestWithXYZ());

listOfOtherType.addAll(listOfFooRequests);
listOfOtherType.addAll(listOfBarRequests);

// Do whatever with the list here.

I know for a fact we'll be adding more types of Requests eventually, probably during the next development cycle. For me to add another Request to the above code would only require adding two lines, but would have to be done by hand, across a large code base leaving a LOT of room for error. I would like to be able to minimize the amount of code that I have to touch to add new Request types to the code base.

My initial idea would be to wrap the dao calls in another service that knows how to get each type of Request, that way I would only need to update that service with each new Request type. Something like the following.

public class BusinessLogicService {
  public List<Request> getAllRequestsWithXYZ() {
    List<Request> returnMe = new ArrayList<>();
    returnMe.addAll(fooRequestDao.findAllFooRequestWithXYZ());
    returnMe.addAll(barRequestDao.findAllBarRequestWithXYZ());
    // eventually returnMe.addAll(quxRequestDao.findAllQuxRequestWithXYZ());
    // ... and so on
    return returnMe;
  }
}

And used like:

List<OtherType> listOfOtherRequests = requestService.transformToOther(businessLogicService.getAllRequestsWithXYZ());

This was my first thought, but I'm sure there are other ways to approach the problem as well; maybe refactoring lower level logic or restructuring some classes. Is this a good way to approach the problem that I have, or should I consider delving deeper into the architecture first?

6
  • If these requests are routinely handled together, why are they in separate lists to begin with?
    – John Wu
    Sep 16, 2019 at 23:25
  • I might be misunderstanding your question, because when they're used they all exist in the same list. That's the listOfOtherType.addAll(findAll[Foo|Bar]WithXYZ).
    – Scrambo
    Sep 18, 2019 at 15:54
  • You're saying they're all in the same list and all being treated like Request? I'm wondering what differentiates the different Request concrete sub classes.
    – Erik Eidt
    Sep 19, 2019 at 16:23
  • 2
    There really nothing in this code that requires that there be distinct concrete subclasses. The FooRequest and BarRequest types are never referenced in this code. They only appear as part of your dao variable names. Similar to what Erik asked: why do you need a FooRequest class and a BarRequest class.
    – JimmyJames
    Sep 19, 2019 at 18:58
  • If it is common to have a single list of all types of requests, why do you have separate DAO classes for each type? Sep 20, 2019 at 8:41

1 Answer 1

3
+50

In general - how to find all derived classes

Reducing your question to its core, we get to the following:

How can I pre-emptively write code for a base class which will automatically take into account any derived classes I make in the future?

The rephrasing should help you realize that this is not going to be easy. How could you pre-emptively reference something that does not yet exist?
This immediately starts raising the reflection red flag in my head.

Note that I call it a red flag because reflection is one of those things that could be indicative of bad design. But there are valid use cases for it, and IMO yours is a valid use case.

How could you pre-emptively reference something that does not yet exist? If you think about it, this is no different from how you can reference a file on the file system before that file is created. There are some ways to do this:

  • Require that the file is created in a specific directory that the application will monitor (this is an analogy for strict namespaces)
  • Require that the file name fits a predetermined pattern and search the entire file system for files that match this pattern (this is an analogy for class name conventions)
  • Require that the file has the correct filetype and search the entire file system for files of this type (this is an analogy for finding all derived classes of a given base class/interface)

The third bullet point clearly relates to what you're trying to do here.

I'm skipping the implementation of this reflection methodology because there are complicated consequences which I'm addressing in the next part.


In general - how to find classes related to the derived classes

So you've written a reflection method which finds all derivations of Request. Great! But for every found type, how are you going to find its repository?

Option 1 - Since you've opened the reflection door, you can freely use it. For each found type (let's call it foundType), you could use reflection to find a class named {foundType}Repository and expect it to have a specific method named GetAll{foundType}s and call that.

But you should see that this is a really bad way to handle it (this is exactly why I consider reflection to be a red flag for good approaches). It's stringly typed and it's going to lead to many bugs whenever a developer doesn't conform to the naming standards.

It would be a lot more type safe if all these repositories had a shared inheritance, so you wouldn't need to guess that they implement the GetAll{foundType}s method.

Option 2 - If we're going the type safe route, the quickest solution would be to have generic repositories, something along the line of:

public class RequestRepository<TRequest> where TRequest : Request
{
    public IEnumerable<TRequest> GetAll() { /* ... */ }
}

Because then, for every foundType you can simply instantiate a RequestRepository<foundType> and use that. This actually handles everything nicely!

However! If all request repositories can be handled using the exact same generic logic, then they really aren't different from each other. If they were different, the different requests would be handled differently at least in some way. You'd expect there to be derived versions of RequestRepository<TRequest> (e.g. FooRequestRepository : RequestRepository<FooRequest>) to account for these different behaviors, and these derived classes would be ignored when you instantiate a RequestRepository<foundType>.

If this generic approach would work, that would mean that a simpler RequestRepository which returns all Request objects (regardless of derived type) would work just as well, which renders your question moot. I can only infer that this is therefore not a viable approach, and that you cannot create a generic RequestRepository<TRequest> to handle all the derived request types.

So how to you find the matching repository for each request type?

Well, this is the same answer as when we asked how to find each request type.

  • Strict namespaces
  • Class name conventions
  • Finding all derived classes of a given base class/interface

As you can see, the problem outset is the same, and you can start repeating the same solution.

In conclusion

Can this be done? Sure. Reflection is a powerful tool, and it's technically possible to do all this.

However, the more you look at it, the more you realize that you're either going to end up with bad practice, stringly typed, naming convention based coding; or you're going to end up realizing that your codebase can be reduced to something dramatically simpler, which defeats the purpose of your implementation.

As the complexity of your codebase increases, the odds of this still being a good idea dramatically decrease.

This isn't a technical limitation, but rather a bad expectation from the developer. You're expecting your codebase to auto-reference classes that you never explicitly referenced. While not impossible, this automated behavior invariably leads to enforcing arbitrary naming/reference standards and unexpected automated behaviors, which very easily start flying in the face of good practice.


However, there is a way...

I happen to be a big fan of Entity Framework, and the exact feature you're asking for is actually something that Entity Framework allows you to do. My experience is with Code First, Table-Per-Type inheritance. I would expect that this also works for the other inheritance strategies, but I haven't yet used them so I can't be sure.

In short, you set it up as follows:

public abstract class Request
{
    public int Id { get; set; }
    public string GeneralRequestValue { get; set; }
}

public class FooRequest : Request
{
    public string SpecificFooValue { get; set; }
}

public class BarRequest : Request
{
    public string SpecificBarValue { get; set; }
}

public class DemoContext : DbContext
{
    public DbSet<Request> Requests { get; set; }
}

While you only reference one DbSet<>, EF will actually make three tables for you:

  • Requests
  • FooRequests
  • BarRequests

Every FooRequest and BarRequest will have their own entry in the Requests table. EF is aware of the shared inheritance and its query methodology allows for inheritance-related lookups. The interesting thing here is that this allows you to do exactly what you want, out of the box:

var fooRequests = db.Set<FooRequest>().ToList();

var barRequests = db.Set<BarRequest>().ToList();

var allRequests = db.Set<Request>().ToList();

allRequests will be of type List<Request>, but the elements in that list will be of the appropriate FooRequest/BarRequest type.

Notice how there is little to no reference to the derived classes. The classes exist by themselves, but EF doesn't ask for you to explicitly reference the classes. If you were to create a BazRequest derived class tomorrow, all you would need to do is create the class and update your database schema (which should not cause conflicts since it will only add a new "BazRequests" table).

In conclusion

This seems to be exactly what you want, but I only know of this behavior in Entity Framework (Code First, TPT). I am unaware of any other libraries which offer the same functionality.

1
  • I know that this comment is super late but I wanted to put a "Thank You" message here, for the time it took to make this response; it's extremely well-written, insightful, and educational. I consider this to be a shining example of what the StackExchange network should be looking for in the answers that contributers provide.
    – Scrambo
    Jun 4, 2020 at 14:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.