Reputation: 361
I'm working on code that has an ever increasing amount of implementations for an interface VendorService
. Right now, where these services are used, we autowire them all in the constructor, leading to long lists of dependencies. Is there a preferred way to handle dependencies when a single interface is repeatedly used?
Current approach:
private final VendorService xVendorService;
private final VendorService yVendorService;
private final VendorService zVendorService;
...
@Autowired
public VendorDelegateService(XVendorService xVendorService,
YVendorService yVendorService,
ZVendorService zVendorService,
...) {
this.xVendorService = xVendorService;
this.yVendorService = yVendorService;
this.yVendorService = yVendorService;
...
}
public void doSomething(VendorId vendorId) {
if (vendorId = VendorId.X) {
xVendorService.doSomething();
} else if (vendorId = VendorId.Y) {
yVendorService.doSomething();
} else if (vendorId = VendorId.Z) {
zVendorService.doSomething();
}
...
}
Clearly this is very verbose and requires updating whenever a new implementation of the interface is created.
An alternative is getting the Bean from the ApplicationContext, something like:
private final ApplicationContext context;
@Autowired
public VendorDelegateService(ApplicationContext context) {
this.context = context;
}
public void doSomething(VendorId vendorId) {
context.getBean(VendorService.class, vendorId.name()).doSomething();
}
This wouldn't require another if/else bracket with every new implementation, but it's obtuse and doesn't feel correct. This logic could of course be abstracted away in its own class to lessen that problem.
Which of these is more idiomatic in Spring and Java? Are there any other approaches I haven't considered?
Upvotes: 1
Views: 337
Reputation: 16775
I feel it is a matter of preference whether there is an idiomatic way for this, but what I suggest is the following solution:
Create an interface for all the services, we can call this VendorService
:
public interface VendorService {
void doSomething();
VendorId getVendorId();
}
Now we would want to implement this interface for all the services, as an example this can be done like this for XVendorService
:
@Service
public XVendorService implements VendorService {
private VendorId vendorId = ....
@Override
public void doSomething() {
...
}
@Override
public VendorId getKey() {
return vendorId;
}
}
Now for the VendorDelegateService
we can do something like this:
@Service
public class VendorDelegateService {
private Map<VendorId, VendorService> services = new HashMap<>();
@Autowired
public AllServices(Set<? extends VendorService> serviceSet) {
serviceSet.stream().forEach(service -> services.put(service.getVendorId(), service));
}
public void doSomething(VendorId vendorId) {
if (services.containsKey(vendorId)) {
services.get(vendorId).doSomething();
}
}
}
Please note that with Set<? extends VendorService> serviceSet
all the services will be autowired automatically. By creating a map afterwards, we are able to dispatch our request to every service based on its vendorKey
.
Upvotes: 4