To set the stage, I am trying to do pure dependency injection for a Java Library I am creating to make it more testable. As it is a library, I want to do pure dependency injection without creating a DI container/composition root as discussed by Mark Seemann in his blog posts*. The problem I am running into is that I have some visitor classes that must contain state (an example illustrating the concept is below). Would it be cleaner to inject these or just declare them using the new keyword? The only problem I see with injecting them is that I would need a function to clear the state of the visitor after every call vs. using new I would not have to. Is there any best practice for this?
public class CarServiceBuilder {
public CarService buildCarService(){
CarPartPrinter carPartPrinter = new CatPartPrinter();
CarService carService = new CarService(carPartPrinter);
return carService;
}
}
public class CarService {
private final CarPartPrinter printParts;
public CarService(CarPartPrinter carPartPrinter){
this.carPartPrinter = carPartPrinter;
}
public void printParts(Car car){
CarVisitor carVisitor = new CarVisitor();
car.accept(carVisitor);
List<Part> parts = carVisitor.getParts();
carPartPrinter.printParts(parts);
}
}
public class CarVisitor extends Visitor {
private List<Parts> parts;
public CarVisitor(){
parts = new ArrayList<>();
}
@Override
public void visit(Part part){
parts.add(part);
}
}
VS.
public class CarServiceBuilder {
public CarService buildCarService(){
CarVisitor carVisitor = new CarVisitor();
CarPartPrinter carPartPrinter = new CatPartPrinter();
CarService carService = new CarService(carVisitor, carPartPrinter);
return carService;
}
}
public class CarService {
private final CarVisitor carVisitor;
private final CarPartPrinter printParts;
public CarService(CarVisitor carVisitor, CarPartPrinter carPartPrinter){
this.carVisitor = carVisitor;
this.carPartPrinter = carPartPrinter;
}
public void printParts(Car car){
car.accept(carVisitor);
List<Part> parts = carVisitor.getParts();
carPartPrinter.printParts(parts);
carVisitor.clearState();
}
}
public class CarVisitor extends Visitor {
private List<Parts> parts;
public CarVisitor(){
parts = new ArrayList<>();
}
@Override
public void visit(Part part){
parts.add(part);
}
public void clearState(){
parts = new ArrayList<>();
}
}
Related blog posts:
Best Answer
I don't typically think of visitors as a dependency that you would inject. Visitors are just a control flow mechanism, an object-oriented switch statement of sorts. Any state within the visitor is analogous to local variables you might otherwise define in the caller's scope. You also usually know exactly what type of visitor is required in a given context (as is the case in your example:
printParts
knows it needs aCarVisitor
to collect a list of parts), so it is simplest to just create a new local instance and throw it away once you're done. Injecting stateful objects creates issues like the one you already noted: you need to remember to clear state once you're done, and it's not thread-safe.As for testing, I would just treat the visitor as an implementation detail of
CarService
and test the functional requirements directly, i.e. doesprintParts
actually print the correct parts, regardless of how it is implemented.