Sunday, October 11, 2015

The Factory Design pattern

The first in our series of design pattern tutorials will be the factory pattern, which is one of the most used design patterns. The factory pattern is a creational pattern which allows the construction of objects without specifying the exact class of the object to be created at design time. During runtime, the appropriate class is selected for creating the object based on the context. The factory pattern allows looser coupling by removing the selection code to a specified class - “the factory”.

We are going to see an implementation of the factory pattern using Java and also how the "factory" class wouldn't be necessary with the use of Java 8.

To showcase with a simple example how the factory pattern could be used, let’s imagine we have a jukebox and the vinyl record is selected based on the button pressed by the user. The interface is the vinyl record, and specifies a single method ‘playMusic’. We have different vinyl records that implement the interface. During runtime, the factory object selects which object (vinyl) to instantiate in order to play music.

The UML class diagram for our project is presented in the figure below:

We start with the VinylRecord interface which will allow us the abstraction in order to select its implementation at runtime:
package com.tasosmartidis.design_patterns_tutorial.observer;

public interface VinylRecord {

    public void playMusic();
}

Next, we will provide the concrete implementations of the VinylRecord for Jazz fans:
package com.tasosmartidis.design_patterns_tutorial.observer;

public class JazzVinyl implements VinylRecord {

    public void playMusic() {
        System.out.println("Playing some smooth jazz...");
        
    }
}

and for Rock & Roll fans:
package com.tasosmartidis.design_patterns_tutorial.observer;

public class RockNRollVinyl implements VinylRecord {

    public void playMusic() {
        System.out.println("Rock n Rolling baby...");
        
    }
}

Now that we have our implementations of the VinylRecord, lets create a factory class which will create the appropriate implementation based on context:
package com.tasosmartidis.design_patterns_tutorial.observer;

public class JukeBox {
    
    public VinylRecord pickVinyl(Integer chosenVinyl) {
        
        switch(chosenVinyl){        
        case 1:
            return new JazzVinyl();
        case 2:
            return new RockNRollVinyl();
        default:
            return null;
        }
    }
}

Finally, we are ready to see our factory pattern implementation in action. We have a simple main method which presents the user with a set of options and waits to receive a valid choice to play some music:
package com.tasosmartidis.design_patterns_tutorial.observer;

import java.util.Scanner;

public class FactoryDemo {

    public static void main(String[] args) {
        JukeBox jukeBox = new JukeBox();
        VinylRecord vinyl;
        
        // The jukebox is in a bar and waits 
        // for the selection of music to play
        System.out.println("What would you like to hear?");
        System.out.println("1. Jazz");
        System.out.println("2. Rock & Roll");
        
        // Get the selection of the user
        Scanner inputReader = new Scanner(System.in);
        boolean selectionMade = false;
        while(!selectionMade) {
            Integer selection = inputReader.nextInt();
            
            if(selection.equals(1) || selection.equals(2)){
                vinyl = jukeBox.pickVinyl(selection);
                selectionMade = true;
                
                if(vinyl!=null) {
                    // Play the song!
                    vinyl.playMusic();
                }else {
                    throw new IllegalArgumentException("Not a valid choice!");
                }
            }
            else 
                System.out.println("You have to press 1 or 2!");
        }
        inputReader.close();        
    }
}

Running the above demo app would look something like this:


And we are done! A simple example to demonstrate the use of the factory pattern. But as we said in the previous post, design patterns do not take into consideration language specifics. How use of Java8 would affect the implementation of the factory design pattern?

We could implement the factory pattern as shown in the main class below:
package com.tasosmartidis.design_patterns_tutorial.observer;

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.function.Supplier;

public class FactoryJava8Demo {
    
    // A map with the constructors of the VinylRecords implementations
    final static Map<Integer,Supplier<VinylRecord>> implementations = 
                    new HashMap<Integer, Supplier<VinylRecord>>();

    public static void main(String[] args) {
        // Add the implementations we have
        implementations.put(1,JazzVinyl::new);
        implementations.put(2,RockNRollVinyl::new);
        
        VinylRecord vinyl;
        
        // The jukebox is in a bar and waits 
        // for the selection of music to play
        System.out.println("What would you like to hear?");
        System.out.println("1. Jazz");
        System.out.println("2. Rock & Roll");
        
        // Get the selection of the user
        Scanner inputReader = new Scanner(System.in);
        boolean selectionMade = false;
        while(!selectionMade) {
            Integer selection = inputReader.nextInt();
            
            if(selection.equals(1) || selection.equals(2)){
                vinyl = createVinyl(selection);
                selectionMade = true;
                // Play the song!
                vinyl.playMusic();
            }
            else 
                System.out.println("You have to press 1 or 2!");
        }
        inputReader.close();

    }
    
    public static VinylRecord createVinyl(Integer selection) {
        Supplier<VinylRecord> vinylSelected = implementations.get(selection);
        if(vinylSelected!=null) 
            return vinylSelected.get();
        else 
            throw new IllegalArgumentException("Not a valid choice!");
    }
}

We have to note though, that this implementation with Java 8 wouldn't necessarily be cleaner or scale better in more complex scenarios, such as having more parameters for the initialization of the implementation classes. You can find the source code in GitHub.

That was the first in the upcoming series of design patterns, so keep in touch!

No comments:

Post a Comment