개발기술/Spring

스프링 도입배경 - 객체지향적 코드개선

bsh6226 2024. 5. 17. 17:18

 

원본 코드 

현상황 분석 : 결제서비스(Class1)가 결제를 위해 머니어뎁터(Class2)를 호출해서 사용하는 단순한 형태였는데,  PayMethod라는 parameter 값에 따라서  Class2(머니어뎁터)를 쓰던지 Class3(카드어뎁터)를 쓰던지 분기하여야함.

 

문제분석 :  if-else문으로 분기하여, Class2(머니어뎁터)와 Class3(카드어뎁터)와 하위 로직들을 끌어와서 사용시 class1 내의 코드 복잡성이 증가함. 추후 분기의 종류 및 class1와 외부클래스 사용점이 늘어나면 외부클래스 종속적인 코드 증가함.

 

해결책 : DIP원칙과 Interface의 다형성 기능을 활용하여 Class1과 Class2,Class3 사이를 interface를 거치어 사용되도록 하면 (class1이 interface에만 의존하도록 하면) 변화가 발생해도 interface만 수정하면 되기에 수정점을 최소화 할 수 있다.

 

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());
    }


}

 

코드개선 - Interface 도입

 

개선된점 :

1. Class2와 Class3를 특정 interface를 상속하도록 하여, interface의 특정반환형식의 메소드를 도입강제화한다. 그러면 class1은 interfae의 특정반환형식에만 맞추어서 코드를 작성하면된다. class1에 끌어와 사용하던  class2, class3의 로직은 Encapsulation된다.

2. Interface 참조변수를 사용함으로써 참조변수에  class2와 class3 중 어떤 child를 주입할지만 분기하면 되어 코드가 Interface 참조변수와 그 메소드 위주로 작성되어 개별 class에 종속성이 줄어들어 간결해짐. 

 

문제점 : 여전히 결제서비스(Class1) 내부에서 Class2(머니어뎁터)와 Class3(카드어뎁터) 객체를 생성하고 if분기에 따라서 PaymenrInterface변수에 객체별로 주입하는 행위를 하고 있어서 는 어떤 Instance를 대입하는 것은 서비스 클래스의 본 역할과(SRP원칙) 맞지 않는다. 또한, 코드가 외부 클래스(Paymethod가 추가되고 Class4가 추가되어야한다면? ConveniencePayService 코드를 수정 필요) 에 의존적이다 

 

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());
    }


}

 

코드개선 - Configuration Class도입

 

개선된 점: 내부에서 객체를 생성해서 Interface에 객체를 주입하는 행위를 하고 싶지 않고, 순수하게 interface만을 사용하여 결제서비스(Class1)를 작성하려면, 별도로 객체를 생성하고 주입해주는 Class를 분리해주어야함. 1. 그리고  결제서비스(Class1) 생성자로 Config class를 활용하여 Interface에 Parameter로 주입할 객체를 받는 코드를 추가한다. 2. Config라는 class를 생성하여 메소드를 통해서 객체를 생성하고 의존관계를 설정해주는 기능을 분리한다. 3. Client 코드는 Configuration class를 호출해서 주입받은 ConveniencePayService를 호출한다.

 

<서비스클래스>

public class MemberServiceImpl implements MemberService {
    private final MemberRepository memberRepository;
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
    public void join(Member member) {
        memberRepository.save(member);
    }
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    } }

 


<Config클래스>

public class AppConfig {
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
    public OrderService orderService() {
        return new OrderServiceImpl(
                memberRepository(),
                discountPolicy());
    }
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
}

 

<Client호출자>

public class OrderApp {
    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService();
        OrderService orderService = appConfig.orderService();
        long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberService.join(member);
        Order order = orderService.createOrder(memberId, "itemA", 10000);
        System.out.println("order = " + order);
    }
}