원본 코드
문제점
- 1. 서비스의 어뎁터 의존관계 복잡도 증가
- 결제서비스가 머니어뎁터를 호출해서 사용하는 1:1 형태 -> request parameter에 따라서 머니어뎁터 / 카드어뎁터를 호출
- 2. 서비스 내 어뎁터의 세부 메소드들 호출
- 머니어뎁터와 카드어뎁터의 세부 구현로직들이 서비스 클래스 내로 호출되어 코드 복잡성이 증가함
- 향후 어뎁터 종류 및 세부 구현로직 증가시 코드 복잡도 증가 예상
package com.zerobase.convpay.service;
import com.zerobase.convpay.dto.PayCancelRequest;
import com.zerobase.convpay.dto.PayCancelResponse;
import com.zerobase.convpay.dto.PayRequest;
import com.zerobase.convpay.dto.PayResponse;
import com.zerobase.convpay.type.*;
public class ConveniencePayService { //편결이
private final MoneyAdapter moneyAdapter = new MoneyAdapter();
private final CardAdapter cardAdapter = new CardAdapter();
public PayResponse pay(PayRequest payRequest) {
CardUseResult cardUseResult;
MoneyUseResult moneyUseResult;
if (payRequest.getPayMethod()== PayMethodType.CARD){
cardAdapter.authorization();
cardAdapter.approval();
cardUseResult = cardAdapter.capture(payRequest.getPayAmount());
}else {
moneyUseResult = moneyAdapter.use(payRequest.getPayAmount());
}
if (cardUseResult==CardUseResult.USE_FAIL||moneyUseResult == MoneyUseResult.USE_FAILED) {
return new PayResponse(PayResult.FAIL, 0);
}
return new PayResponse(PayResult.SUCCESS, payRequest.getPayAmount());
}
public PayCancelResponse payCancel(PayCancelRequest payCancelRequest) {
MoneyUseCancelResult moneyUseCancelResult = moneyAdapter.useCancel(payCancelRequest.getPayCancelAmount());
if (moneyUseCancelResult == MoneyUseCancelResult.MONEY_USE_CANCEL_FAILED) {
return new PayCancelResponse(PayCancelResult.PAY_CANCEL_FAILED, 0);
}
return new PayCancelResponse(PayCancelResult.PAY_CANCEL_SUCCESS, payCancelRequest.getPayCancelAmount());
}
}
코드개선1 : Interface 도입 ;
해결책
- 클래스간 강한결합을 끊어냄 : Interface 도입을 통해서 DIP 적용
- 의존관계의 클래스들 사이를 interface 거치어 호출하도록 하면 구현체 내부의 변화가 발생해도 interface를 활용한 코드는 변경이 필요없음. 대신 구현체 내부에는 특정 메소드의 구현을 강제화하여 통일성을 강화시킴.
문제점
- 서비스 내 어뎁터의 세부 메소드들 호출문제는 해결
- request의 parameter에 따라 인터페이스에 구현체를 주입하는 코드로 인해 클래스간 결합도 여전히 존재,
- 서비스 클래스의 본 역할과(SRP원칙) 맞지 않는다, 어뎁터가 한가지 더 추가된다면 서비스 클래스를 수정해야함.
package com.zerobase.convpay.service;
import com.zerobase.convpay.dto.PayCancelRequest;
import com.zerobase.convpay.dto.PayCancelResponse;
import com.zerobase.convpay.dto.PayRequest;
import com.zerobase.convpay.dto.PayResponse;
import com.zerobase.convpay.type.*;
public class ConveniencePayService { //편결이
private final MoneyAdapter moneyAdapter = new MoneyAdapter();
private final CardAdapter cardAdapter = new CardAdapter();
public PayResponse pay(PayRequest payRequest) {
PaymentInterface paymentInterface;
if (payRequest.getPayMethod()== PayMethodType.CARD){
paymentInterface = cardAdapter;
}else {
paymentInterface = moneyAdapter;
}
if (paymentInterface.payment(payRequest.getPayAmount())==PaymentResult.PAYMENT_FAILED) {
return new PayResponse(PayResult.FAIL, 0);
}
return new PayResponse(PayResult.SUCCESS, payRequest.getPayAmount());
}
public PayCancelResponse payCancel(PayCancelRequest payCancelRequest) {
PaymentInterface paymentInterface;
if (payCancelRequest.getPayMethodType()== PayMethodType.CARD){
paymentInterface = cardAdapter;
}else {
paymentInterface = moneyAdapter;
}
CancelPaymentResult cancelPaymentResult = paymentInterface.cancelPayment(payCancelRequest.getPayCancelAmount());
if (cancelPaymentResult == CancelPaymentResult.CANCEL_PAYMENT_FAILED) {
return new PayCancelResponse(PayCancelResult.PAY_CANCEL_FAILED, 0);
}
return new PayCancelResponse(PayCancelResult.PAY_CANCEL_SUCCESS, payCancelRequest.getPayCancelAmount());
}
}
코드개선2 : Configuration Class도입
해결책
- 별도로 객체를 생성하고 주입해주는 역할을 별도 Class로 분리해주어야함
- Config라는 class를 생성하여 메소드를 통해서 객체를 생성하고 의존관계를 설정해주는 기능을 분리한다.
- 결제서비스(Class1) 생성자로 Config class를 활용하여 Interface에 Parameter로 주입할 객체를 받는 코드를 추가한다.
- Client 코드는 Configuration class를 호출해서 주입받은 ConveniencePayService를 호출한다
public class UserClient {
public static void main(String[] args) {
//설정클래스로 스프링컨테이너 생성
ApplicationContext applicationContext
= new AnnotationConfigApplicationContext(ApplicationConfig.class);
//컨테이너로부터 의존성을 주입받음(
ConveniencePayService conveniencePayService
= applicationContext.getBean("conveniencePayService",
ConveniencePayService.class);
@Component
public class ConveniencePayService { //편결이
private Map<PayMethodType,PaymentInterface> paymentInterfaceMap = new HashMap(); // interface class와 payment.method를 맵핑
private DiscountInterface discountInterface;
// ConveniencePayService 생성자 도입으로, paymentinterface로 사용할 set과
// discountinterface를 받음
public ConveniencePayService(Set<PaymentInterface> paymentInterfaceSet,
@Qualifier("discountByConvenience")
DiscountInterface discountInterface) {
paymentInterfaceSet.forEach(paymentInterface
-> paymentInterfaceMap.put(
paymentInterface.getPayMethod(),paymentInterface));
this.discountInterface = discountInterface;
}
public PayResponse pay(PayRequest payRequest) {
// 결제수단을 판별하여 그에 맞는 어뎁터 호출하여 인터페이스에 대입하여 결제로직을 처리한다
PaymentInterface paymentInterface = paymentInterfaceMap.get(payRequest.getPayMethod());
// 결제로직을 처리하는 어뎁터와 별개로 금액할인은 별도의 인터페이스를 활용하여 사용한다.
Integer discountedAmount = discountInterface.getDiscountedAmount(payRequest);
// 할인금액을 받아서 어뎁터별로 결제로직을 돌리고 실패시에 에러메세지를 반환한다.
if (paymentInterface.payment(discountedAmount) == PaymentResult.PAYMENT_FAILED) {
return new PayResponse(PayResult.FAIL, 0);
}
return new PayResponse(PayResult.SUCCESS, discountedAmount);
}
'개발기술 > Spring' 카테고리의 다른 글
스프링 전체구조 (0) | 2024.07.24 |
---|---|
Java 프로젝트 초기 환경설정 (스프링 부트, Package, Configuration) (0) | 2024.07.23 |
스프링 MVC, 예외처리 (0) | 2024.07.18 |
스프링 부가기능 (Resource, AOP, 유효성검증, 데이터바인딩,spel) (0) | 2024.07.17 |
Spring의 핵심 개념 (IoC, DI) (0) | 2024.05.10 |