javadesign-patternsfactory-pattern

Why do we use factory class to instantiate interface


I'm currently reading book pro spring 6. I am a bit confused on one example from book

Hello World Application

Here is the main class:

public class HelloWorldDecoupledWithFactory {

    public static void main(String... args) {
        MessageRenderer mr = MessageSupportFactory.getInstance().getMessageRenderer()
                .orElseThrow(() -> new IllegalArgumentException("Service of type 'MessageRenderer' was not found!"));
        MessageProvider mp = MessageSupportFactory.getInstance().getMessageProvider()
                .orElseThrow(() -> new IllegalArgumentException("Service of type 'MessageProvider' was not found!"));
        mr.setMessageProvider(mp);
        mr.render();
    }
}

The book mentions, that there is a small problem "changing the implementation of either the MessageRenderer or MessageProvider interface means a change to the code. To get around this we need to delegate the responsibility of retrieving the two implementation types and instantiating them to somebody else. The most manual one is to create a simple factory class, that reads the implementation class names from a properties file and instantiates them on behalf of the application"

I'm a bit confused on changing the implementation of either the MessageRenderer or MessageProvider interface means a change to the code. For example, if we add an additional method to interface, the classes implement it will change. So what is purpose using Factory?

public class MessageSupportFactory {

    private static MessageSupportFactory instance;
    private Properties props;
    private MessageRenderer renderer;
    private MessageProvider provider;

    private MessageSupportFactory() {
        props = new Properties();
        try {
            props.load(this.getClass().getResourceAsStream("/msf.properties"));
            String rendererClass = props.getProperty("renderer.class");
            String providerClass = props.getProperty("provider.class");
            renderer = (MessageRenderer) Class.forName(rendererClass).getDeclaredConstructor().newInstance();
            provider = (MessageProvider) Class.forName(providerClass).getDeclaredConstructor().newInstance();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    static {
        instance = new MessageSupportFactory();
    }

    public static MessageSupportFactory getInstance() {
        return instance;
    }

    public Optional<MessageRenderer> getMessageRenderer() {
        return renderer != null?  Optional.of(renderer) : Optional.empty();
    }

    public Optional<MessageProvider> getMessageProvider() {
        return provider!= null?  Optional.of(provider) : Optional.empty();
    }
}

Solution

  • Let's say you have 2 classes that have MessageRenderer as an object and you have the following interface implementation:

    public class MessageRendererImpl implements MessageRenderer {}
    
    
    public class HelloWorldExample1 {
    
       private MessageRenderer render;
    
       public HelloWorldExample1() {
         render = new MessageRendererImpl();
       }
    }
    
    public class HelloWorldExample2 {
    
       private MessageRenderer render;
    
       public HelloWorldExample2() {
         render = new MessageRendererImpl();
       }
    }
    

    So in this case if you want to create another MessageRenderer implementation class MessageRendererImplNew, you should change the name in every class. So in the this case you the Factory class do the initiation by only reading the class name from the properties file, which you change from one place only.