위 책을 보면서 정리한 글입니다.
비슷한 객체를 조건에 따라 별도로 생성하여 관리하는 것은 매우 비효율적이다.
예를 들어, 결제 시스템에서 시스템의 확장으로 결제 방식이 변경되는 경우 분기문 내부 코드도 수정되어야 하면, 이는 코드의 결합도가 매우 높은 구조라고 할 수 있다.
이릴 경우 객체 생성이 객체 추가와 삭제 등의 수정에 유연하지 않게 된다.
해결 방법 : 결제 방식에 따른 결제 객체 생성과 관리를 별도의 관리 객체에 위임하여 객체의 추가나 삭제시 유연하게 대처 가능하다.
결제 방식에 따라 결제 객체의 생성과 수행이 분기문 내에서 처리되는건 나쁜 방식일까
- 조건에 따라 결제 방식이 달라지는건 어쩔 수 없지만, 결제 방식에 따라 결제를 담당하는 클래스가 모두 영향을 받는다면 결제 방식이 변경될 때마다 검증이 필요하게 된다.
- 의도하지 않은 예외 사항이 발생하며 수정이나 개선에 대한 확신과 정확성을 보장 불가능.
생성만 별도로 분리하면 결제 실행은 어떻게 진행할까
- 결제 객체들은 공통의 인터페이스로 상속하면 결제 요청 객체에서는 인터페이스로 결제 실행을 처리.
- 결제 객체를 받는 요청객체에서는 어떤 객체를 반환하는지 알 필요 없이 결제만 수행 가능
별도의 생성 관리 개체를 사용하지 않고, 기존의 결제 요청 객체 내에서 결제 객체를 생성하는 메서드를 사용해도 되지 않을까
- 기존의 객체는 결제를 요청하고 응답을 처리하는 기능을 가졌는데 이는 결제 객체 생성과는 무관한 기능
- 나중에 다른곳에서 객체 생성을 재사용하는 상황이 생기면 엉뚱한 곳에서 객체 생성을 담당한다고 생각할 수 있다.
TotalPaymentGate는 결제 요청 시 전달받은 결제 방식에 따라 그에 맞는 객체를 생성하여 결제를 처리하는 구조
결제 정보를 받아 결제 처리를 관리하는 클래스
requsetPay()로 결제를 요청하면 결제 방식에 해당하는 객체(ARSRequestor, MobileRequestor, CardRequestor)를 생성
결제 객체의 request()를 호출하여 결제 처리를 위임
사용자가 결제를 요청하면 해당하는 결제를 처리한 후 결과를 반환
전달받은 결제 방식을 추출하여 분기문을 통해 객체를 생성하고 처리를 요청
public class TotalPaymentGate {
public PayResultTO requestPay(PayRequestTO requestTO) {
String payMethod = rquestTo.getPaymethod();
Map<String, Object> options = new HashMap<String, Object>();
int amount = requstTO.getAmount();
String productName = requestTO.getProductName();
Map<String, Onject> result;
if(payMethod.eqauls("card")) {
// 카드 결제 요청
CardRequestor cardRequestor = new CardRequestor();
result = cardRequestor.request(amount, productName);
} else if(payMethod.eqauls("mobile")) {
// 모바일 결제 요청
MobileRequestor MobileRequestor = new MobileRequestor();
result = mobileRequestor.requset(amount, productName);
} else if(payMethod.eqauls("ars")) {
// ARS 결제 요청
ARSRequestor arsRequestor = new ARSRequestor();
result = arsRequestor.request(amount, productName);
} else {
// 무응답 처리
result = new HashMap<String, Object>();
}
PayResultTO payResultTO = parsePayResultTO(payMethod, result);
return payResult;
}
private PayResultTO parsePayResultTO(String payMethod, Map<String, Object> result) {
// 중략
}
}
public class ARSRequestor { // 대표로 ARS만
public Map<String, Object> request(int amount, String ProductName) {
// 결제 처리
// 중략
Map<String, Object> result = new HashMap<String, Object>();
result.put("code", 0);
result.put("msg", productName + "을 " + amount + "에 ARS로 구매");
return result;
}
}
위 코드의 문제점
1. 결제 방식에 따른 결제 객체의 생성과 처리 요청을 TotalPaymentGate에서 관리.
2. 결제 방식에 따라서 결제 객체를 생성하고 관리하므로 TotalPaymentGate는 결제 객체의 수정에 의존적.
3. 결제 방식에 따른 생성과 처리가 분기문에 의한 처리로 관리되는 로직은 결제 방식이 증가하거나 수정이 잦으면 손 댈수 없는 클래스로 변하게된다.
개선 후, 결제 객체와는 상관없이 RequestorFactory와 연관되어, 결제 객체와의 결합도가 제거된다.
public interface Requsetor {
public Map<String, Object> request(PayResultTO payResultTO);
}
public class ARSRequestor implements Requsetor{ // 대표로 ARS만
// 결제 처리
@Override
public Map<String, Object> request(PayRequestTO payRequestTO) {
int amount = payRequestTO.getProductName();
String ProductName = payRequestTO.getProductName();
return reqeust(amount, productName);
}
public Map<String, Object> request(int amount, String ProductName) {
// 중략
}
}
기존 TotalPyamentGate에서 다음과 같이 결제 객체를 생성하는 로직을 추출하여 분리
- RequestFactory 클래스를 만든다.
- 생성 로직을 분리한 후 RequestorFactory.createRequesor() 메서드로 이동.
- 결제 객체의 동작 코드는 삭제하고, 생성된 객체를 반환하도록 수정.
public class RequestFactory {
public static Requestor createRequestor(String payMethod) {
// 반환 객체
Requestor requestor;
if(payMethod.eqauls("card")) {
// 카드 결제 요청
requestor = new CardRequestor();
} else if(payMethod.eqauls("mobile")) {
// 모바일 결제 요청
requestor = new MobileRequestor();
} else if(payMethod.eqauls("ars")) {
// ARS 결제 요청
requestor = new ARSRequestor();
} else {
// 무응답 처리
requestor = null;
}
return requestor;
}
}
public class RequestorFactoryTest {
@Test
public void testCreateRequsetor() throws Exception {
// Card 결제 객체 생성
Requestor cardRequsetor = RequestorFactory.testCreateRequsetor("card");
// Null 체크
Assert.assertNotNull(cardRequsetor);
// 예상한 클래스 타입인지 확인
Assert.assertEquals(CareRequestor.class, requestor.getClass());
}
}
public class TotalPaymentGate {
public PayResultTO requestPay(PayRequestTO requestTO) {
String payMethod = rquestTo.getPaymethod();
// 생성 관리 객체로부터 결제 수단별 결제 객체 전달
Requestor requestor = RequestorFactory.createRequestor(payMethod);
// 전달받은 추상화 객체로 결제 수행
Map<String, Object> result = requestor.request(requestTO);
PayResultTO payResultTO = parsePayResultTO(payMethod, result);
return payResult;
}
private PayResultTO parsePayResultTO(String payMethod, Map<String, Object> result) {
// 중략
}
}
팩토리 패턴은 비슷한 특성을 지닌 객체들의 생성을 관리할 때 주로 사용.
- 분기문을 통한 객체의 생성 로직과 비즈니스 로직이 혼재되어 있는지 확인
- 조건문에 따른 생성 객체의 유사성을 확인한 후 추상 객체를 생성.
- 객체 생성을 관리하는 팩토리 객체를 생성하여 해당 객체에서 객체의 생성을 관리.
- 기존 분기문을 제거하고 팩토리 객체를 통해 객체를 전달받아 사용.