Prerequisites: Understanding UML Class Diagrams
Before diving into design patterns, it's essential to understand UML class diagrams. They provide a visual representation of class structures and relationships, which is fundamental to grasping how design patterns work. If you're unfamiliar with UML notation, consider reviewing a basic UML class diagram guide first.
Overview of the Simple Factory Pattern
The Simple Factory Pattern is a creational design pattern that encapsulates object creation logic within a single factory class. This approach centralizes instantiation logic and promotes loose coupling between clients and concrete product classes.
UML Class Diagram
The following diagram illustrates the structure of our calculator implementation using the Simple Factory Pattern. The diagram shows the abstract base class, four concrete implementations, and the factory that creates instances based on input parameters.
Implementation Steps
Step 1: Create the Abstract Base Class
First, we define an abstract operation class that contains common properties and defines the contract for all concrete implementations.
/**
* Abstract base class for all arithmetic operations
*/
public abstract class Operation {
private double firstOperand = 0;
private double secondOperand = 0;
public double getFirstOperand() {
return firstOperand;
}
public void setFirstOperand(double firstOperand) {
this.firstOperand = firstOperand;
}
public double getSecondOperand() {
return secondOperand;
}
public void setSecondOperand(double secondOperand) {
this.secondOperand = secondOperand;
}
/**
* Template method for calculating results
* @return computed result
*/
public abstract double calculate();
}
Note that we use the abstract keyword to mark this class as incomplete, requiring subclasses to provide specific implementations.
Step 2: Implement Concrete Operation Classes
Next, we create four concrete classes, each handling one arithmetic operation:
/**
* Addition operation implementation
*/
public class AdditionOperation extends Operation {
@Override
public double calculate() {
return getFirstOperand() + getSecondOperand();
}
}
/**
* Subtraction operation implementation
*/
public class SubtractionOperation extends Operation {
@Override
public double calculate() {
return getFirstOperand() - getSecondOperand();
}
}
/**
* Multiplication operation implementation
*/
public class MultiplicationOperation extends Operation {
@Override
public double calculate() {
return getFirstOperand() * getSecondOperand();
}
}
/**
* Division operation implementation
*/
public class DivisionOperation extends Operation {
@Override
public double calculate() {
if (getSecondOperand() == 0) {
throw new ArithmeticException("Cannot divide by zero");
}
return getFirstOperand() / getSecondOperand();
}
}
Step 3: Create the Factory Class
The factory class contains the logic for determining which concrete class to instantiate based on the operator symbol provided:
/**
* Factory for creating operation instances
*/
public class OperationFactory {
/**
* Creates an operation instance based on the given operator
* @param operator mathematical operator symbol
* @return appropriate operation instance
*/
public static Operation createOperation(String operator) {
Operation result = null;
switch (operator) {
case "+":
result = new AdditionOperation();
break;
case "-":
result = new SubtractionOperation();
break;
case "*":
result = new MultiplicationOperation();
break;
case "/":
result = new DivisionOperation();
break;
default:
throw new IllegalArgumentException("Unsupported operator: " + operator);
}
return result;
}
}
Step 4: Client Code Usage
Here's how clients use the factory to perform calculations:
/**
* Demonstration of Simple Factory Pattern usage
*/
public class Client {
public static void main(String[] args) {
Operation operation = OperationFactory.createOperation("/");
operation.setFirstOperand(10);
operation.setSecondOperand(3);
System.out.println("Result: " + operation.calculate());
Operation addition = OperationFactory.createOperation("+");
addition.setFirstOperand(5);
addition.setSecondOperand(7);
System.out.println("Result: " + addition.calculate());
}
}
Key Benefits of This Pattern
Encapsulation: Object creation logic is isolated within the factory Maintainability: Adding new operations requires changes only to the factory Testability: Concrete classes can be easily mocked for testing Loose Coupling: Client code depends only on abstractions, not concrete implementations
Considerations
While the Simple Factory Pattern offers several advantages, it does have a limitation: the factory class itself violates the Open/Closed Principle because it must be modified whenever new operation types are added. For scenarios requiring more flexibility, consider exploring the Factory Method or Abstract Factory patterns instead.