1

This is more of a design question than a code implementation but I am hoping someone can help out.

The issue I am facing is I will need to inject a modelSerivce depending on two inputs. I am pretty sure based on some documentation and SO questions I want to use a factorybean for this.

My question goes more into the construction of these classes that will be created with the factorybean. How can I re-use a singleton bean with the factory instead of creating a new class every time the factory is called?

Here is what the code looks like:

Thing Interface:

public interface Thing {
    void Run();
}

ThingA Implementation:

public class ThingA implements Thing{
    public void Run() {
        System.out.println("In ThingA");
    }
}

ThingB Implementation:

public class ThingB implements Thing{
    public void Run() {
        System.out.println("In ThingB");
    }
}

ThingFactory Implementation:

public class ThingFactory {
    public Thing GetThing(String stateCode, Date date){
        Thing result;
        if(stateCode == "MA") {
            result = new ThingA();
        }
        else {
            result = new ThingB();
        }
        return result;
    }
}

What i really want the factory to do is pull a known implementation instead of creating an implementation each time the factory is called. I also would like to not tie my factory to the Spring framework by doing something like this:

ApplicationContext.getBean()
Jamie Babineau
  • 746
  • 2
  • 12
  • 25

3 Answers3

2

Actually I dealt with the same problem and here is the solution I came up with. The main trick that I relied to is that any bean that is defined as singletone is instanciated by Spring at the time of Spring initialization. So here is what I did:

  1. In your case add an abstract class ThingImpl that implements your Thing interface and will be a parent to all your ThnigA, ThingB ... classes. (Alternatively just change your Thing interface into abstract Class)
  2. Change your ThingFactory to something like this:

        public class ThingFactory {
            private static Map<String, Thing> instancesMap = new Hashmap<>()
            public static Thing getThing(String name) {
              return instancesMap.get(name);
            }      
        public static addInstance(String name, Thing thing) {
          instancesMap.put(name, thing);
        }
    

    }

  3. In your abstract parent add the following constructor

public ThingImpl() { ThingFactory.addInstance(this.getClass().getSimpleName(), this); }

Your factory doesn't need to be defined as bean at all, but all your Thing classes should be defined as beans. What will happen is that when Spring initializes it will instanciate all your Thing classes and as part of its own initialization each Thing class will insert itself into a map in your factory. So all you have to do now is anywhere in your code you can call your Factory:

ThingFactory.getThing("ThingA");
ThingFactory.getThing("ThingB");
Michael Gantman
  • 7,315
  • 2
  • 19
  • 36
1

Just keep the beans in spring context as singletons as you normally would. Inject the context into your factory and pull beans from it based on your business rules.

public class ThingFactory {

    @Autowired 
    private ApplicationContext ctx;

    public Thing GetThing(String stateCode, Date date){
        Thing result;
        if(stateCode == "MA") {
            result = ctx.getBean("someBean")
        }
        else {
            result = ctx.getBean("someOtherBean")
        }
        return result;
    }
}

You can be even more clever and use a scheme to name the beans on the context:

@Service("Thing_MA")
public class ThingA implements Thing{
.
.
.
}

This buys you nice declarative look up rules for your factory:

public class ThingFactory {
    public Thing GetThing(String stateCode, Date date){
        return (Thing) ctx.getBean("Thing_" + stateCode);

   }
}
Robert Moskal
  • 21,737
  • 8
  • 62
  • 86
  • Robert, injecting context into your code makes your code Spring aware which is a disadvantage. See my answer how to do the same without using context – Michael Gantman Aug 16 '17 at 15:13
  • Sure. I think if I'm using spring I'd rather be dependent on spring than to have all of my things have to know about the factory. It seems to me you are just reproducing the spring context anyway. But your way works, I would try to sway you to my way. – Robert Moskal Aug 16 '17 at 15:31
  • Actually, my way aderes to non-intrucive principle which Spring incourages. See here: https://stackoverflow.com/questions/15333033/what-is-the-exact-meaning-of-invasive-and-what-makes-spring-non-invasive. However, I also have to say that your solution works as well. Stll firmly believe that mine is better :) – Michael Gantman Aug 16 '17 at 16:07
  • We'll have to agree to disagree. I think you are creating an unnecessary class hierarchy. I can totally imagine wanting to instantiate POJOs of your things without reference to the factory and so it already seems like a bad code smell! You are contaminating your core business classes with a more or less arbitrary implementation/life cycle concern. In my approach only the factory is dependent on spring, which is seems obvious and clear. – Robert Moskal Aug 17 '17 at 22:28
0

If you don't want to tie your code spring framework it means you don't want to BeanFactory and create your own factory then you will have to create objects by your self like

public class ThingFactory {

    private final static Thing thingA = new ThingA();
    private final static Thing thingB = new ThingB();

    public Thing GetThing(String stateCode, Date date){
        if(stateCode.equals("MA")) {
            return thingA;
        } else {
            return thingB;
        }
    }
}

And if you have more implementations of Thing in that case you simply create a map and get the object according to your need.

public class ThingFactory {

    private final static Map<String, Thing> beanMap = new Hashmap<>()

    public ThingFactory(){
        addThing("ThingA", new ThingA());
        addThing("ThingB", new ThingB());
    }

    public static Thing getThing(String name) {
      return beanMap.get(name);
    }      

    public static addThing(String name, Thing thing) {
       beanMap.put(name, thing);
    }

    public Thing GetThing(String stateCode, Date date){
        if(stateCode.equals("MA")) {
            return getThing("ThingA");
        } else {
            return getThing("ThingB");
        }
    }
}

However, I suggest you to go with Spring's BeanFactory instead of creating your own factory and go with @Robert Moskal answer.

And if the value of stateCode is known at application startup time you can use spring profiles to achieve so.

@Service
@Profile("ThingB")
public class ThingB implements Thing{
    public void Run() {
        System.out.println("In ThingB");
    }
}
Naresh Joshi
  • 4,188
  • 35
  • 45