Using Service as "Factory" to Return Different Bean Implementations

Viewed 380

I want to use this type of service class in my Spring application as a "factory" to return the correct implementation of DocumentProcessor in response to a supplied enum value.

I'm doing this because I want to set up each processor itself as a component and take advantage of autowiring vs just creating new instances when each processor is needed.

I haven't really seen this done anywhere else - Can anyone tell me if this is a bad idea?

All "TypeXYZDocumentProcessor" classes are extensions of an abstract "DocumentProcessor" base class.

@Service
public class DocumentProcessorService {

  @Autowired
  TypeXDocumentProcessor typeXDocumentProcessor;

  @Autowired
  TypeYDocumentProcessor typeYDocumentProcessor;

  @Autowired
  TypeZDocumentProcessor typeZDocumentProcessor;

  public DocumentProcessor getDocumentProcessor(DocumentType docType) {
    switch (docType) {
      case TYPE_X:
        return typeXDocumentProcessor;
      case TYPE_Y:
        return typeYDocumentProcessor;
      case TYPE_Z:
        return typeZDocumentProcessor;
      default:
        return null;
    }
  }
}  

@Component
public class TypeXDocumentProcessor extends DocumentProcessor {
  ...
}

public abstract class DocumentProcessor {
  ...
}
3 Answers

This is my proposition, I used Interface instead of abstract class, but if you really need an abstract class you can return on it.

@Service
public class DocumentProcessorService {

  @Autowired
  // you can add here for examlpe a @Qualifier("typeXDocumentProcessor"),
  // then name your variable whatever you want.
  DocumentProcessor typeXDocumentProcessor;

  @Autowired
  DocumentProcessor typeYDocumentProcessor;

  @Autowired
  DocumentProcessor typeZDocumentProcessor;

  public DocumentProcessor getDocumentProcessor(DocumentType docType) {
    switch (docType) {
      case TYPE_X:
        return typeXDocumentProcessor;
      case TYPE_Y:
        return typeYDocumentProcessor;
      case TYPE_Z:
        return typeZDocumentProcessor;
      default:
        return null;
    }
  }
}  

@Component
public class TypeXDocumentProcessor implements DocumentProcessor {
  ...
}
@Component
public class TypeYDocumentProcessor implements DocumentProcessor {
  ...
}
@Component
public class TypeZDocumentProcessor implements DocumentProcessor {
  ...
}

public interface class DocumentProcessor {
  ...
}

You may also do something like this. I've modified your abstract class DocumentProcessor to include the DocumentType. Code is not tested or compiled.

This way, you can keep introducing more processors types and not touch the processor service at all.

@Service
public class DocumentProcessorService {

  @Autowired
  List<DocumentProcessor> documentProcessors;

  public DocumentProcessor getDocumentProcessor(DocumentType docType) {
    return documentProcessors.stream().filter(e -> e.getDocType().equals(docType)).findFirst().get();
  }
}

@Component
public class TypeXDocumentProcessor extends DocumentProcessor {

  public TypeXDocumentProcessor() {
    super(TYPE_X);
  }
}

// More Types...

public abstract class DocumentProcessor {
  ...

  DocumentType docType; 

  public DocumentProcessor(DocumentType docType) {
    this.docType = docType;
  }
  DocumentType getDocType() {
    return docType;

  }
}

I propose going about this a different way. Let's replace switch or if blocks with polymorfism (not tested):

public enum DocumentType {

    TYPE_X("typeXDocumentProcessor"),
    TYPE_y("typeYDocumentProcessor");

    private DocumentProcessor processor;
    private final String beanName;

    DocumentType (String beanName){
      this.beanName = beanName;
    }
    public String process(){
        return processor.process();
    }


    @Component
    public static class DocumentTypeInjector {
        @Autowired
        private ApplicationContext context;

        @PostConstruct
        public void postConstruct() {
            for (DocumentType dt : EnumSet.allOf(DocumentType.class))
               dt.processor = context.getBean(dt.beanName)
        }
    }
}

And then your service won't need the getDocumentProcessor method at all. You would just have a process method for example, calling the process of the current enum instance

Related