Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructing multi-level object tree with manual dependency injection

I am working on refactoring a Java application that I wrote. At the time of writing the original, I made some poor design decisions, and am now suffering the consequences. In particular, the code is nearly impossible to unit test, and dependencies are all over the place.

I have been doing a lot of reading about design patterns and good OO-design principles (including SOLID). I think the key to solving my problems lies in the following: constructor dependency injection (ideally NOT via a framework like Spring; I'd rather stick to plain old Java), factories, builders, and good OO-design in general.

Without getting into too many specifics, imagine I have an object structure where O1 has one or more O2 child objects, and each O2 object has one or more O3 child objects, and so on. So, essentially, O1 is a parent of O2, and O2 is a parent of O3. (But not in the inheritance sense.)

The dependencies I want to represent are of the child to the parent. So, for example, injecting the dependencies into the constructor might result in something like this:

// highest-level object; no dependencies
O1 o1a = new O1();

// second-level objects; depend on highest-level object
O2 o2a = new O2(o1a);
O2 o2b = new O2(o1a);

// 3rd-level objects; depend on second-level objects
O3 o3a = new O3(o2a); // dependency on o2a
O3 o3b = new O3(o2a); // dependency on o2a
O3 o3c = new O3(o2b); // dependency on o2b

OK, so that in itself is simple enough. But there are some complicating factors:

  • Each of these objects is potentially quite complex to construct (some involve the parsing of schemas and the building of in-memory object models associated with the objects);
  • I won't know at compile time how many of each object will be required (this is configuration dependent); as such, manually coding dependencies (as above) is not an option;
  • I don't want to "new" specific classes, because I want the code to be extensible, and want to be able to "stub" in different versions for testing; (factories?);
  • I need to be able to start the creation process at the "lowest level"; for example, I may know at run-time that I need to create a specific O3 object; to do this requires determining O3's "parent" (O2), and then O2's parent (O1), then creating O1, creating O2 (injecting newly-created O1), creating O3 (injecting newly-created O2), etc.
  • I might also, potentially, want to keep track of objects so that they can be reused; so, in the example above, o3a and o3b both make use of o2a; if o2a was created in the course of creating o3a, I'd want to reuse the same object when creating o3b.

In all my reading of design patterns, unfortunately, I have never seen any example of how to accomplish something even vaguely like this. (All examples are quite simple by comparison.)

I have wondered about creating some sort of factory or builder for O3, and passing it an instance of a factory or builder for O2. That instance of O2 factory or builder would, in turn, have been created with an instance of a factory or builder for O1. But I'm not sure if this is the right approach, and even if it is, I'm struggling to get much beyond the high-level concept.

I would greatly appreciate some guidance on how to accomplish all of this in a clean, maintainable, extensible way. It's a bit of a tall order, I know.

Thanks in advance,
Greg

like image 362
Gurtz Avatar asked Oct 29 '25 05:10

Gurtz


1 Answers

I started out with basically the same problems as you are. I wanted a pure Java "Injection" solution without magic, so I created JayWire. Even if you don't plan to use this library, you may want to look at the Wiki for some ideas.

Specifically, the basic idea was to inject factories (java.util.function.Supplier) instead of constructed objects. That way, the consuming class does not have to know whether the object is a singleton, pooled, or constructed every time (new). It goes like this:

public class O3 {
    private Supplier<O2> o2Supplier;

    public O3(Supplier<O2> o2Supplier) {
        this.o2Supplier = o2Supplier;
    }

    public void doSomething() {
        ...
        // Here O3 wants an O2 instance, but does not
        // need to know how to construct it, or even
        // whether its constructed, or the same instance
        // is re-used.
        O2 o2 = o2Supplier.get();
        o2.doIt(...);
        ...
    }
}

This way the hierarchy is "hidden" from O3, it only knows it needs O2, but doesn't necessarily need to know how O2 is constructed.

You can of course change Supplier to Factory (or whatever), although a Factory implies it will always create a new instance.

You even might have some parameters at the construction of O2 that are local, but still want to hide the fact that it needs O1 from the consumer O3. This can also be done this way:

public class O3 {
    private Supplier2<String, Integer, O2> o2Supplier;

    public O3(Supplier2<String, Integer, O2> o2Supplier) {
        this.o2Supplier = o2Supplier;
    }

    public void doSomething() {
        ...
        // Here O3 wants an O2 instance again, but also
        // wants additional parameters for its construction.
        O2 o2 = o2Supplier.get("Param1", 5);
        o2.doIt(...);
        ...
    }
}

Supplier2 is similar to a Supplier, it only takes 2 additional parameters to its get() method.

This way, O3 still does not know that O2 needs O1, so the hierarchy can be broken up. You may want to look at "Propagating Dependencies" in the Wiki.

JayWire enables all this, and also helps you create these Suppliers easily.

like image 97
Robert Bräutigam Avatar answered Oct 30 '25 23:10

Robert Bräutigam



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!