Factory patterns are creational design patterns that provide an interface for creating objects in a superclass, while allowing subclasses to alter the type of objects that will be created. These patterns enhance flexibility by encapsulating object creation logic.
There are three main types of factory patterns:
- Simple Factory Pattern: A centralized approach for creating objects, but not ideal for creating families of related products.
- Factory Method Pattern: Defines an interface for creating an object, but lets subclasses decide which class to instantiate. This pattern allows for creating different variants of a product.
- Abstract Factory Pattern: Provides an interface for creating families of related or dependent objects without specifying their concrete clases. This pattern is useful when you need to ensure that your products are compatible, but it makes adding new product types difficult.
I. Simple Factory Pattern Implementation
Let's implement a simple factory pattern for creating vehicles. First, we'll define a Vehicle interface with a move operation:
1 package com.patterns.factory;<br></br>2 <br></br>3 public interface Vehicle {<br></br>4 public void move();<br></br>5 }
Next, we'll create concrete implementations of this interface:
1 package com.patterns.factory;<br></br>2 <br></br>3 public class Airplane implements Vehicle{<br></br>4 <br></br>5 @Override<br></br>6 public void move() {<br></br>7 System.out.println("Soaring through the skies...");<br></br>8 }<br></br>9 <br></br>10 }
Now, let's create an abstract factory class that defines the creation method:
1 package com.patterns.factory;<br></br>2 <br></br>3 public abstract class VehicleFactory {<br></br>4 public abstract Vehicle create();<br></br>5 }
Implement a concrete factory for creating Airplane objects:
1 package com.patterns.factory;<br></br>2 <br></br>3 public class AirplaneFactory extends VehicleFactory{<br></br>4 <br></br>5 @Override<br></br>6 public Vehicle create() {<br></br>7 return new Airplane();<br></br>8 }<br></br>9 }
Here's how the client code would use this factory:
1 package com.patterns.factory;<br></br>2 <br></br>3 public class Client {<br></br>4 public static void main(String[] args) {<br></br>5 <br></br>6 VehicleFactory factory = new AirplaneFactory();<br></br>7 Vehicle vehicle = factory.create();<br></br>8 vehicle.move();<br></br>9 }<br></br>10 <br></br>11 }
If we need to introduce a different vehicle, we can easily extend this pattern. Let's create a Broom implementation:
1 package com.patterns.factory;<br></br>2 <br></br>3 public class Broom implements Vehicle{<br></br>4 <br></br>5 @Override<br></br>6 public void move() {<br></br>7 System.out.println("Rushing through the dust storm...");<br></br>8 }<br></br>9 <br></br>10 }
And its corresponding factory:
1 package com.patterns.factory;<br></br>2 <br></br>3 public class BroomFactory extends VehicleFactory{<br></br>4 <br></br>5 @Override<br></br>6 public Vehicle create() {<br></br>7 return new Broom();<br></br>8 }<br></br>9 <br></br>10 }
The client code can then switch between vehicle types by changing the factory:
1 VehicleFactory factory = new BroomFactory();
This demonstrates how the simple factory pattern allows for flexible creation of new product types.
II. Factory Method Pattern
Here's an implementation of the factory method pattern:
interface Product {
public void operation();
}
class ConcreteProduct implements Product {
public void operation() {
System.out.println("Product operation");
}
}
interface Creator {
public Product factoryMethod();
}
class ConcreteCreator implements Creator {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
public class Client {
public static void main(String[] args) {
Creator creator = new ConcreteCreator();
Product product = creator.factoryMethod();
product.operation();
}
}
III. Abstract Factory Pattern Implementation
Now, let's implement the abstract factory pattern for creating families of related products. In this example, we'll create vehicles, weapons, and food items.
First, define the abstract product classes:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public abstract class Vehicle {<br></br>4 public abstract void move();<br></br>5 }
Concrete vehicle implementation:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public class Car extends Vehicle{<br></br>4 <br></br>5 @Override<br></br>6 public void move() {<br></br>7 System.out.println("Driving to the northeast...");<br></br>8 }<br></br>9 }
Abstract weapon class:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public abstract class Weapon {<br></br>4 public abstract void fire();<br></br>5 }
Concrete weapon implementation:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public class Rifle extends Weapon {<br></br>4 <br></br>5 @Override<br></br>6 public void fire() {<br></br>7 System.out.println("Bang bang bang...");<br></br>8 }<br></br>9 }
Abstract food class:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public abstract class Food {<br></br>4 public abstract void consume();<br></br>5 }
Concrete food implementation:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public class Fruit extends Food {<br></br>4 <br></br>5 @Override<br></br>6 public void consume() {<br></br>7 System.out.println("Eating fresh fruit");<br></br>8 }<br></br>9 }
Now, define the abstract factory interface:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public abstract class AbstractFactory {<br></br>4 public abstract Vehicle createVehicle();<br></br>5 public abstract Weapon createWeapon();<br></br>6 public abstract Food createFood();<br></br>7 }
Create a concrete factory implementation:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public class StandardFactory extends AbstractFactory {<br></br>4 <br></br>5 @Override<br></br>6 public Vehicle createVehicle() {<br></br>7 return new Car();<br></br>8 }<br></br>9 <br></br>10 @Override<br></br>11 public Weapon createWeapon() {<br></br>12 return new Rifle();<br></br>13 }<br></br>14 <br></br>15 @Override<br></br>16 public Food createFood() {<br></br>17 return new Fruit();<br></br>18 }<br></br>19 }
The client code can use this factory to create and use related products:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public class Client {<br></br>4 public static void main(String[] args) {<br></br>5 AbstractFactory factory = new StandardFactory();<br></br>6 <br></br>7 Vehicle vehicle = factory.createVehicle();<br></br>8 vehicle.move();<br></br>9 Weapon weapon = factory.createWeapon();<br></br>10 weapon.fire();<br></br>11 Food food = factory.createFood();<br></br>12 food.consume();<br></br>13 }<br></br>14 }
To switch to a different family of products, we can create another factory:
Alternative vehicle implementation:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public class Broom extends Vehicle {<br></br>4 <br></br>5 @Override<br></br>6 public void move() {<br></br>7 System.out.println("Flying through the sandstorm...");<br></br>8 }<br></br>9 }
Alternative weapon implementation:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public class MagicStaff extends Weapon {<br></br>4 <br></br>5 @Override<br></br>6 public void fire() {<br></br>7 System.out.println("Casting magic spells...");<br></br>8 }<br></br>9 }
Alternative food implementation:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public class Mushroom extends Food {<br></br>4 <br></br>5 @Override<br></br>6 public void consume() {<br></br>7 System.out.println("Cooking mushroom soup");<br></br>8 }<br></br>9 }
Create a new factory for this product family:
1 package com.patterns.factory.abstractfactory;<br></br>2 <br></br>3 public class MagicFactory extends AbstractFactory{<br></br>4 <br></br>5 @Override<br></br>6 public Vehicle createVehicle() {<br></br>7 return new Broom();<br></br>8 }<br></br>9 <br></br>10 @Override<br></br>11 public Weapon createWeapon() {<br></br>12 return new MagicStaff();<br></br>13 }<br></br>14 <br></br>15 @Override<br></br>16 public Food createFood() {<br></br>17 return new Mushroom();<br></br>18 }<br></br>19 }
The client can then switch to the new product family by changing the factory:
1 AbstractFactory factory = new MagicFactory();
IV. Spring-style Factory Pattern Implementation
Here's how to implement a factory pattern similar to Spring's approach using configuration files.
First, define a Moveable interface and its implementations:
1 package com.spring.factory;<br></br>2 <br></br>3 public interface Moveable {<br></br>4 public void move();<br></br>5 }
1 package com.spring.factory;<br></br>2 <br></br>3 public class Car implements Moveable{<br></br>4 <br></br>5 @Override<br></br>6 public void move() {<br></br>7 System.out.println("Driving to the northeast...");<br></br>8 }<br></br>9 }
Add another implementation:
1 package com.spring.factory;<br></br>2 <br></br>3 public class Train implements Moveable {<br></br>4 <br></br>5 @Override<br></br>6 public void move() {<br></br>7 System.out.println("Chugging along with smoke...");<br></br>8 }<br></br>9 <br></br>10 }
Create a configuration file (application.properties):
transportType=com.spring.factory.Train
The client code uses reflection to create objects based on the configuration:
1 package com.spring.factory;<br></br>2 <br></br>3 import java.io.InputStream;<br></br>4 import java.util.Properties;<br></br>5 <br></br>6 public class Client {<br></br>7 public static void main(String[] args) throws Exception {<br></br>8 Properties properties = new Properties();<br></br>9 InputStream input = Client.class.getClassLoader().getResourceAsStream(<br></br>10 "com/spring/factory/application.properties");<br></br>11 properties.load(input);<br></br>12 String transportTypeName = properties.getProperty("transportType");<br></br>13 Object obj = Class.forName(transportTypeName).newInstance();<br></br>14 <br></br>15 Moveable vehicle = (Moveable) obj;<br></br>16 vehicle.move();<br></br>17 }<br></br>18 }
To switch between implementations, simply modify the configuration file:
transportType=com.spring.factory.Car
This approach allows for easy switching between implementations without modifying the client code.