Java Library – How to do Pure Dependency Injection When State is a Factor

dependency-injectiondesign-patternsjavaobject-oriented-designstate

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 a CarVisitor 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. does printParts actually print the correct parts, regardless of how it is implemented.