
펙토리 메서드 패턴은 객체 생성의 책임을 서브클래스에게 위임하여 객체의 생성 방식을 변경할 수 있는 디자인 패턴입니다.인터페이스나 추상 클래스를 통해 객체를 생성하는 방법을 정의합니다.팩토리 메서드 패턴에서 생성되는 객체의 공통 인터페이스를 정의합니다.AbstractOperation 이 해당됩니다.구체적인 클래스들입니다.AddIntegerOperation, AddDoubleOperation 등이 해당됩니다.인터페이스 또는 추상클래스입니다.팩토리 인터페이스를 구현하여 구체적인 제품을 생성하는 클래스입니다.CalculatorIntegerFactory , CalculatorDoubleFactory 해당됩니다.+---------------------+
| Product |
+---------------------+
| +operation() |
+---------------------+
^
|
+---------------------+
| ConcreteProductA |
+---------------------+
| +operation() |
+---------------------+
^
|
+---------------------+
| ConcreteProductB |
+---------------------+
| +operation() |
+---------------------+
+---------------------+
| Creator |
+---------------------+
| +factoryMethod() |
+---------------------+
^
|
+---------------------+
| ConcreteCreatorA |
+---------------------+
| +factoryMethod() |
+---------------------+
^
|
+---------------------+
| ConcreteCreatorB |
+---------------------+
| +factoryMethod() |
+---------------------+
팩토리 메서드에서 캡슐화하여, 클라이언트 코드가 객체 생성 방식에 신경 쓸 필요가 없습니다.클라이언트 코드의 변경 없이 팩토리 메서드만 수정하면 됩니다.구체적인 클래스에 의존하지 않고, 인터페이스나 추상 클래스에 의존하기 때문에 클래스간의 결합도가 낮아집니다.public interface AbstractOperation<T extends Number> {
T operator(T firstNumber, T secondNumber);
}
double, int 어떤 타입이 들어올지 모르기때문에 제네릭 타입으로 메소드를 정의해줍니다.public class AddIntegerOperation implements AbstractOperation<Integer> {
@Override
public Integer operator(Integer firstNumber, Integer secondNumber) {
return firstNumber + secondNumber;
}
}
AbstactOperation<Integer> 인터페이스를 상속받아 구현한 구현체입니다.Interger 타입으로 메소드를 수행하기 때문에 제네릭 타입을 명시해줍니다.public class AddDoubleOperation implements AbstractOperation<Double> {
@Override
public Double operator(Double firstNumber, Double secondNumber) {
return firstNumber + secondNumber;
}
}
AbstactOperation<Double> 인터페이스를 상속받아 구현한 구현체입니다.
Double 타입으로 메소드를 수행하기 때문에 제네릭 타입을 명시해줍니다.
각 연산자에 맞춰 나머지 연산자에 대한 구현도 해줍니다.
public enum CalculatorIntegerFactory {
ADDINTEGER("+", new AddIntegerOperation()),
SUBINTEGER("-", new SubIntegerOperation()),
MULINTEGER("*", new MulIntegerOperation()),
DIVINTEGER("/", new DivIntegerOperation());
private final String operator;
private final AbstractOperation<Integer> abstractOperation;
CalculatorIntegerFactory(String operator, AbstractOperation<Integer> abstractOperation) {
this.operator = operator;
this.abstractOperation = abstractOperation;
}
public static AbstractOperation<Integer> operation(String operator) throws Exception {
for (CalculatorIntegerFactory value : values()) {
if(value.operator.equals(operator)) return value.abstractOperation;
}
return null;
}
}
Factory는 enum 클래스로 구현합니다.연산자, AbstactOperation 을 주입받습니다.operation 메서드를 통해 연산자와 일치하는 enum 상수를 찾습니다.enum 상수가 있으면 그에 해당하는 AbstractOperation<Integer> 객체를 반환합니다.Double 타입도 동일하게 enum 클래스로 구현합니다.public class ExceptionHandler {
private final String firstNum;
private final String secondNum;
private final String operator;
private static final String OPERATION_REG = "[+\\-*/]";
public ExceptionHandler(String firstNum, String secondNum, String operator) {
this.firstNum = firstNum;
this.secondNum = secondNum;
this.operator = operator;
}
public void validation() throws Exception {
boolean firstType = new TypeValidation().type(firstNum);
boolean secondType = new TypeValidation().type(secondNum);
if (!Pattern.matches(OPERATION_REG, this.operator)) throw new Exception("연산자 타입이 맞지 않습니다.");
if ((firstType && !secondType) || (!firstType && secondType)) throw new Exception("숫자 타입이 서로 맞지 않습니다.");
if (this.operator.equals("/") && secondType && Double.parseDouble(secondNum) == 0.0)
throw new Exception("0으로 나눌 수 없습니다.");
if (this.operator.equals("/") && !secondType && Integer.parseInt(secondNum) == 0)
throw new Exception("0으로 나눌 수 없습니다.");
}
}
if (!Pattern.matches(OPERATION_REG, this.operator))if ((firstType && !secondType) || (!firstType && secondType))if (this.operator.equals("/") && secondType && Double.parseDouble(secondNum) == 0.0)0.0 일경우 예외를 발생시킵니다.if (this.operator.equals("/") && !secondType && Integer.parseInt(secondNum) == 0)0 일 경우 얘외를 발생시킵니다.public class ArithmeticCalculator {
private final List<Number> result = new ArrayList<>();
public Number calculation(String firstNumber, String secondNumber, String operator) throws Exception {
new ExceptionHandler(firstNumber, secondNumber, operator).validation();
boolean isDoubleType = new TypeValidation().type(firstNumber);
if (isDoubleType) {
AbstractOperation<Double> doubleAbstractOperation = CalculatorDoubleFactory.operation(operator);
Double answer = doubleAbstractOperation.operator(Double.parseDouble(firstNumber), Double.parseDouble(secondNumber));
result.add(answer);
return answer;
}
AbstractOperation<Integer> integerOperation = CalculatorIntegerFactory.operation(operator);
Integer answer = integerOperation.operator(Integer.parseInt(firstNumber), Integer.parseInt(secondNumber));
result.add(answer);
return answer;
}
public void getResult() {
AtomicInteger idx = new AtomicInteger(0);
result.forEach(number -> {
int cnt = idx.incrementAndGet();
System.out.println(cnt + "번째 연산결과 :" + number);
});
}
펙토리 메서드를 통해 객체를 생성하고 List 에 결과를 저장하는 로직이 담겨있는 클래스입니다.new ExceptionHandler() 를 통해 타입 검증을 수행합니다.AbstractOperation<T> 타입을 생성해줍니다.List에 저장해줍니다.getResult() 메소드는 lamda를 통해 저장한 연산결과를 출력해주는 메서드입니다.📖 톺아보기
팩토리 메서드 패턴을 통해 객체 생성 방식을 선택할 수 있었습니다.단일 책임 원칙 (SRP)개방/페쇄 원칙 (OCP)의존 역전 원칙(DIP)객체 지향 프로그래밍에 대해 조금 더 다가가는 계기가 되었습니다.