Search code examples
javaspringfactory-pattern

Where do we achieve loose coupling using spring framework?


I am learning spring framework.I read many tutorials from the website, but I could not get their explanation. Please explain me in an easy way and briefly.

  1. Here I put Factory design pattern to achieve loose coupling and how do we use this design pattern in Spring too.

  2. I could not get this point(sentence) "this pattern provides one of the best ways to create an object".

    public interface Shape {
           void draw();
        }
    
    public class Rectangle implements Shape {
    
       @Override
       public void draw() {
          System.out.println("Inside Rectangle::draw() method.");
       }
    }
    
    public class Square implements Shape {
    
       @Override
       public void draw() {
          System.out.println("Inside Square::draw() method.");
       }
    }
    
    public class Circle implements Shape {
    
       @Override
       public void draw() {
          System.out.println("Inside Circle::draw() method.");
       }
    }
    
    public class ShapeFactory {
    
       //use getShape method to get object of type shape 
       public Shape getShape(String shapeType){
          if(shapeType == null){
             return null;
          }     
          if(shapeType.equalsIgnoreCase("CIRCLE")){
             return new Circle();
    
          } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
             return new Rectangle();
    
          } else if(shapeType.equalsIgnoreCase("SQUARE")){
             return new Square();
          }
    
          return null;
       }
    }
    
    public class FactoryPatternDemo {
    
       public static void main(String[] args) {
          ShapeFactory shapeFactory = new ShapeFactory();
    
          //get an object of Circle and call its draw method.
          Shape shape1 = shapeFactory.getShape("CIRCLE");
    
          //call draw method of Circle
          shape1.draw();
    
          //get an object of Rectangle and call its draw method.
          Shape shape2 = shapeFactory.getShape("RECTANGLE");
    
          //call draw method of Rectangle
          shape2.draw();
    
          //get an object of Square and call its draw method.
          Shape shape3 = shapeFactory.getShape("SQUARE");
    
          //call draw method of circle
          shape3.draw();
       }
    }
    

    OUTPUT:

    Inside Circle::draw() method.
    Inside Rectangle::draw() method.
    Inside Square::draw() method.
    

Solution

  • Here you use a classic factory that creates new instances at each invocation. But the factory misses two points : getShape() should provide a static method and the factory class should not be instantiatiable more than once.

    Using singleton and static methods for factory classes brings some drawbacks : mocking during test is more complicated, the factory increases its responsibilities (it is a singleton but it is also a factory class), a high coupling is creating between classes of the application : client classes and the factory.
    The dependency injection that Spring provides(but it is not alone) addresses these concerns.
    Spring takes indeed the role of the factory and the singleton concern is handled by Spring too.

    In Spring, you have some ways of doing a thing similar enough :

    • using factory-bean and factory-method. You have an XML and a Java version.
      The XML way is the XML way : verbose and not necessarily suitable if you prefer annotate directly your classes rather than creating indirection to read the used Spring configuration.
      The Java version has not the indirection drawback but it is a little more verbose as your factory class has to implement the Spring FactoryBean interface.

    • using a classic Spring bean annotated with the prototype scope.

    The equivalent in Spring could be :

    @Bean
    @Scope("prototype")
    public Shape shape(String shapeType) {   
          if(shapeType == null){
             return null;
          }     
          if(shapeType.equalsIgnoreCase("CIRCLE")){
             return new Circle();
    
          } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
             return new Rectangle();
    
          } else if(shapeType.equalsIgnoreCase("SQUARE")){
             return new Square();
          }
    
          return null;   
    }
    

    In any way, you should use the Object getBean(String name, Object... args) method of BeanFactory or ApplicationContext to transmit the shapeType argument.

    For example :

    Shape shape = (Shape) applicationContext.getBean("shape", "CIRCLE");