Factory Design Pattern: A Practical Approach for Developers

Md Sadek Sultan

18 September, 2024

Implementing the Factory Design Pattern in an Online Ticket Application

Imagine a world whereby changing code is effortless; creating new objects requires merely flicking a switch, and software designs work in as much harmony as a well-oiled machine. Welcome to the world with the Factory Design Pattern, where that kind of sheer power is put into the hands of an astute developer. In this post, we’ll explore how this pattern can transform your approach toward coding and make your applications more scalable, maintainable, and future-proof by implementing the Factory Pattern to handle different types of tickets in an online ticketing application.

Why Use the Factory Pattern?

The Factory Pattern provides a way to encapsulate the instantiation logic of objects, making the code more flexible and extensible. When new entities are added to an application, such as different types of tickets, the Factory Pattern simplifies the object creation process and reduces tight coupling between classes.

Scenario: Online Ticket Application

Imagine we’re developing an online ticket application. Initially, it supported only bus tickets. Later, we decided to add train and air tickets. Without the Factory Pattern, our code would become cluttered with conditional statements to handle creating different ticket types. Let’s see how we can solve this problem using the Factory Pattern.

Without the Factory Method, we are implementing a demo:
				
					public class GenerateTicket {


   public static void main(String[] args) {
       User user = new User(2);
       System.out.println(user.getVehicle().bookTicket());
   }
}

				
			

First we create a class called GenerateTicket. In this class, we are creating an instance of a Bus class.

				
					public class Bus implements IVehicleInterface {
   public String bookTicket() {
       return "Bus Ticket Booked";
   }
}

				
			

Bus Class implements VehicleInterface with implementing bookTickets method.

				
					public interface IVehicleInterface {
   public String bookTicket();
}

				
			

Now, If we need to add a Train ticket, we will need to add a Train class and implement an interface like a Bus class.

				
					public class Train implements IVehicleInterface{


   @Override
   public String bookTicket() {
       return "Train Ticket Booked";
   }
}

				
			

Now we need to add conditional statements for creating bus or train objects from GenerateTicket Class.

Now create a User class:
				
					public class User {
   private IVehicleInterface vehicle;


   public User(int type) {
       if (type == 1) {
           vehicle = new Bus();
       } else if (type == 2) {
           vehicle = new Train();
       }else {
           vehicle = null;
       }
   }


   public IVehicleInterface getVehicle() {
       return vehicle;
   }
} 

				
			

We need to add a condition for every vehicle that will be added soon.

Now, what will happen if we add a factory pattern?

Step 1: Define the Common Interface

First, we create an interface IVehicleInterface that all ticket types will implement

				
					public interface IVehicleInterface {
   public String bookTicket();
}

				
			

Step 2: Create Concrete Classes for Each Ticket Type

Next, we create classes for each ticket type, implementing the IVehicleInterface.

Bus.java

				
					public class Bus implements IVehicleInterface {
   public String bookTicket() {
       return "Bus Ticket Booked";
   }
}

				
			

Train.java

				
					public class Train implements IVehicleInterface {
   public String bookTicket() {
       return "Train Ticket Booked";
   }
}

				
			

Step 3: Define an Enumeration for Vehicle Types

				
					public enum VehicleEnum {
   VT_BUS,
   VT_TRAIN,
   VT_AIR
}

				
			

Step 4: Create the Factory Class

The VehicleFactory class will have a static method to create objects based on the vehicle type.

				
					public class VehicleFactory {
   public static IVehicleInterface build(VehicleEnum vehicleEnum) {
       switch (vehicleEnum) {
           case VT_BUS:
               return new Bus();
           case VT_TRAIN:
               return new Train();
           case VT_AIR:
               // return new Air(); // Placeholder for Air class
           default:
               throw new IllegalArgumentException("Unknown vehicle type: " + vehicleEnum);
       }
   }
}

				
			

Step 5: Modify the User Class

The User class will use the VehicleFactory to build the appropriate vehicle.

				
					public class User {
   private IVehicleInterface vehicle;
   public void buildVehicle(VehicleEnum vehicleEnum) {
       this.vehicle = VehicleFactory.build(vehicleEnum);
   }
   public IVehicleInterface getVehicle() {
       return vehicle;
   }
}

				
			

Step 6: Create the Main Class to Generate Tickets

Finally, we create a main class, GenerateTicket, to demonstrate the use of the Factory Pattern.

				
					
public class GenerateTicket {
   public static void main(String[] args) {
       User user = new User();
       user.buildVehicle(VehicleEnum.VT_TRAIN);
       System.out.println(user.getVehicle().bookTicket());
   }
}

				
			
Conclusion

By using the Factory Pattern, we’ve made our online ticket application more flexible and maintainable. Adding new ticket types in the future will be straightforward, as we only need to implement the IVehicleInterface and update the VehicleFactory class. This pattern helps to keep the code clean and reduces tight coupling, making our application more accessible to manage and extend.

Today is a huge day. We will cover another design pattern in another blog. Good luck to all of you.

Md Sadek Sultan

18 September, 2024