8.3. Factory Pattern
The Factory Pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
Simple Factory Pattern
java
// Product interface
interface Shape {
void draw();
}
// Concrete products
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Circle");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Rectangle");
}
}
class Triangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Triangle");
}
}
// Simple Factory
class ShapeFactory {
public Shape createShape(String shapeType) {
return switch (shapeType.toLowerCase()) {
case "circle" -> new Circle();
case "rectangle" -> new Rectangle();
case "triangle" -> new Triangle();
default -> throw new IllegalArgumentException("Unknown shape type: " + shapeType);
};
}
}
Factory Method Pattern
java
// Creator abstract class
abstract class Dialog {
public void render() {
Button button = createButton();
button.render();
button.onClick();
}
// Factory method
public abstract Button createButton();
}
// Concrete creators
class WindowsDialog extends Dialog {
@Override
public Button createButton() {
return new WindowsButton();
}
}
class WebDialog extends Dialog {
@Override
public Button createButton() {
return new HTMLButton();
}
}
// Product interface
interface Button {
void render();
void onClick();
}
// Concrete products
class WindowsButton implements Button {
@Override
public void render() {
System.out.println("Rendering Windows style button");
}
@Override
public void onClick() {
System.out.println("Windows button clicked!");
}
}
class HTMLButton implements Button {
@Override
public void render() {
System.out.println("Rendering HTML button");
}
@Override
public void onClick() {
System.out.println("HTML button clicked! Navigating to URL...");
}
}
Abstract Factory Pattern
java
// Abstract Factory
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
// Concrete Factories
class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
class MacFactory implements GUIFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public Checkbox createCheckbox() {
return new MacCheckbox();
}
}
// Additional products
interface Checkbox {
void render();
void toggle();
}
class WindowsCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Rendering Windows checkbox");
}
@Override
public void toggle() {
System.out.println("Windows checkbox toggled");
}
}
class MacCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Rendering Mac checkbox");
}
@Override
public void toggle() {
System.out.println("Mac checkbox toggled");
}
}
// Client code
class Application {
private Button button;
private Checkbox checkbox;
public Application(GUIFactory factory) {
button = factory.createButton();
checkbox = factory.createCheckbox();
}
public void render() {
button.render();
checkbox.render();
}
}
Practical Factory Pattern Example
java
// Database connection factory
interface DatabaseConnection {
void connect();
void disconnect();
void executeQuery(String query);
}
class MySQLConnection implements DatabaseConnection {
@Override
public void connect() {
System.out.println("Connecting to MySQL database...");
}
@Override
public void disconnect() {
System.out.println("Disconnecting from MySQL database...");
}
@Override
public void executeQuery(String query) {
System.out.println("Executing MySQL query: " + query);
}
}
class PostgreSQLConnection implements DatabaseConnection {
@Override
public void connect() {
System.out.println("Connecting to PostgreSQL database...");
}
@Override
public void disconnect() {
System.out.println("Disconnecting from PostgreSQL database...");
}
@Override
public void executeQuery(String query) {
System.out.println("Executing PostgreSQL query: " + query);
}
}
class OracleConnection implements DatabaseConnection {
@Override
public void connect() {
System.out.println("Connecting to Oracle database...");
}
@Override
public void disconnect() {
System.out.println("Disconnecting from Oracle database...");
}
@Override
public void executeQuery(String query) {
System.out.println("Executing Oracle query: " + query);
}
}
// Connection Factory
class DatabaseConnectionFactory {
public static DatabaseConnection createConnection(String dbType) {
return switch (dbType.toLowerCase()) {
case "mysql" -> new MySQLConnection();
case "postgresql" -> new PostgreSQLConnection();
case "oracle" -> new OracleConnection();
default -> throw new IllegalArgumentException("Unsupported database type: " + dbType);
};
}
// Factory method with configuration
public static DatabaseConnection createConnectionFromConfig() {
// In real application, read from configuration file
String dbType = System.getProperty("database.type", "mysql");
return createConnection(dbType);
}
}
// Usage
public class DatabaseDemo {
public static void main(String[] args) {
DatabaseConnection connection = DatabaseConnectionFactory.createConnection("mysql");
connection.connect();
connection.executeQuery("SELECT * FROM users");
connection.disconnect();
}
}
Factory Pattern Benefits
- Loose coupling between client and products
- Single Responsibility Principle - creation logic in one place
- Open/Closed Principle - easy to introduce new products
- Code maintainability - centralized object creation
When to Use Factory Pattern
- When you don't know exact types of objects beforehand
- When you want to provide extension points for subclasses
- When you need to decouple object creation from usage
- When you have complex object creation logic