-
Notifications
You must be signed in to change notification settings - Fork 5
Propagating Dependencies
Defining dependencies in one object only to be propagated to the construction of another object is considered an anti-pattern. It usually looks like this:
public class ServiceA {
// ServiceC is only a dependency because SomeComponent needs
// it. This is bad practice!
public ServiceA(ServiceB serviceB, ServiceC serviceC, ...) {
...
}
...
public void someMethod() {
...
new SomeComponent(serviceC) // Propagating serviceC
...
}
...
}
public class SomeComponent {
public SomeComponent(ServiceC serviceC) {
...
}
...
}Assuming serviceC is not used anywhere else in ServiceA, but only to be used in the construction of SomeComponent, it is not a real dependency and it should not be defined.
This has several unwanted consequences:
- ServiceA has to be modified each time the SomeComponent construction changes
- Dependencies will potentially multiply as each component has to get all the dependencies of constructed components recursively
- Constructor parameters have to be potentially threaded through multiple layers of constructors
The solution to the above problem is to abstract the construction of components out into specialized classes, also known as factories. Instead of depending on the dependencies of the constructed object, the parent component has to depend only on a factory (or actually a Supplier) class that can construct said component:
public class ServiceA {
public ServiceA(ServiceB serviceB, Supplier<SomeComponent> someComponentFactory, ...) {
...
}
public void someMethod() {
...
someComponentFactory.get()
...
}
...As a Supplier is a functional interface, it can be implemented using a lambda expression. In a JayWire module you would write this the following way:
public Supplier<SomeComponent> getSomeComponentFactory() {
return () -> new SomeComponent(getServiceC());
}
public ServiceA getServiceA() {
return new ServiceA(getServiceB(), getSomeComponentFactory(), ...);
}Now, if the construction of SomeComponent changes, ServiceA does not have to be modified.
Sometimes constructing an object involves not only parameters that need to be "injected" (services, other components), but also values needed for the construction of the specific instance. For example, what if SomeComponent is defined this way:
public class SomeComponent {
public SomeComponent(ServiceC serviceC, String str1, String str2) {
...
}
...
}In this case serviceC would need to be injected from JayWire, but both string parameters would have to be given at the construction site. A normal Supplier would not fit this case, because it only defines a single get() method without parameters.
There are however additional Supplier classes defined in JayWire which can infact take parameters, these are the classes Supplier1 to Supplier9, which take 1 to 9 parameters. For the above case, for taking 2 parameters, Supplier2 is needed, which is defined this way:
public interface Supplier2<P1, P2, T> {
T get(P1 p1, P2 p2);
}With this ServiceA would be written like this:
public class ServiceA {
public ServiceA(ServiceB serviceB, Supplier2<String, String, SomeComponent> someComponentFactory, ...) {
...
}
public void someMethod() {
...
someComponentFactory.get(str1, str2)
...
}
...And the definition in a JayWire module would simply be:
public Supplier2<String, String, SomeComponent> getSomeComponentFactory() {
return (str1, str2) -> new SomeComponent(getServiceA(), str1, str2);
}Through these factories all the injected dependencies can be hidden, and all the user-site parameters for the construction can be made explicit.
If the produced factory needs to be serializable, you can use the makeSerializable method like this:
public Supplier2<String, String, SomeComponent> getSomeComponentFactory() {
return makeSerializable( (str1, str2) -> new SomeComponent(getServiceA(), str1, str2) );
}The serialization of factories works the same way as the serialization of suppliers described on the Serialization page. You have to extend the interface SerializationSupport to use this method.