본문 바로가기

Java

SOLID 설계원칙 예제와 AppConfig 활용

1. 서론

Java공부를 하다 보면 꼭들어보는 단어 '객체지향'

내가 간단하게 정리한 객체지향은 "어떤 프로젝트를 개발하였을때 서로 연결되어 있지 않고 로봇 처럼 각각의 부품들을 조립하여 한 프로젝트가 완성되는 것"

 

2. 객체지향의 설계원칙(SOLID)

여기서 또 많이 듣는 원칙 객체 지향의 설계원칙(SOLID) 다섯 가지의 원칙을 앞 글자만 딴 법칙

- SRP(단일 책임의 원칙 : Single Responsibility Prinicple)

   한 클래스는 하나의 책임만 가져야 한다

 

- OCP(개방폐쇄의 원칙 : Open Close Principle)

   확장에는 열려있고, 수정에는 닫혀있어야 한다

 

- LSP(리스코브 치환의 원칙 : The Liskov Substitution Principle)

   상위 타입은 하위타입으로 대체 할 수 있어야 한다

 

- ISP(인터페이스 분리의 원칙 : Interface Segregation Principle)

  인터페이스 내에 메소드는 최소한 일수록 좋다(하나의 일반적인 인터페이스 보다 여러개의 구체적인 인터페이스가 낫다)

 

- DIP(의존성역전의 원칙 : Dependency Imversion Prinicple)

   구체적인 클래스보다 상위클래스, 인터페이스, 추상 클래스와 같이 변하지 않을 가능성이 높은 클래스와 관계를 맺어라

 

면접 준비 하면서 달달 외웠지만... 실전 적용0% 예제 없이는 이해 못하는 사람...

이번에 인강들으면서 감이 쫌 잡아서 정리한다

인강 -> 인프런 스프링 핵심원리 - 기본편(김영한)

 

3. 예제

주문 할인 서비스를 개발하고자 한다.

근데 조건이 있다.

1. 회원 등급이 vip만 할인 해줌

2. 고정금액 할인, 퍼센테이지 할인 두가지 할인 방법이 존재 -> 뭐 선택할지 모름

3. 또 다른 할인 방법이 생길수도, 할인을 안 해줄수도 있음

 

DiscountPolicy 

public interface DiscountPolicy {
    int discount(Member member, int price);
}

할인 정책의 인터페이스!!! 구현 객체가 아니다!!

우리는 회원 등급에 따라 할인율이 달라지니 회원 정보와 물건의 원 가격을 알아야 함.

 

FixDiscountPolicy, RateDiscountPolicy

public class RateDiscountPolicy implements DiscountPolicy{

    private int discountPercent = 10;
    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) return price * discountPercent / 100;
        else return 0;
    }
}
public class FixDiscountPolicy implements  DiscountPolicy{
    private int DiscountFixAmount = 1000;
    @Override
    public int discount(Member member, int price) {
        if( member.getGrade() == Grade.VIP) return DiscountFixAmount;
        else return 0;
    }
}

위에서 설정한 인터페이스를 받아 Override 하여 구현한다

RateDiscountPolicy -> 회원 등급이 vip면 10% 할인 

FixDiscountPolicy ->회원 등급이 vip면 천원 할인 

 

OrderServiceImpl

public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();
    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    
    @Override
    public Order crateOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

 

 

주문 구현하는 클래스 이것도 인터페이스에 추상화 시켜 @Override하여 구현해준다.

주문할때 회원 정보, 아이템 이름, 아이템 가격과 할인 금액은 우리가 만든 discoutPolicy 정책을 활용하여 계산해준다.

 

근데...여기서 문제점이 생김..

 

문제점

private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

 

이 두줄을 보면

MemberRepository와 DiscountPolicy를 보면 내가 정함....

위에 우리가 말했던 SOLID 설계원칙에 대입해보자

 

SRP -> 이 클래스는 주문해주는 클래스여서 주문만!! 해주는 클래스인데 MemberRepository도 뭘 선택할까 클래스가 정하고 DiscountPolicy도 뭘 선택할까 클래스가 정함...하나의 클래스가 여러가지 담당하고 있음...탈락

OCP -> 확장에는 열려있음 근데 수정에도 열려있음.. 왜냐면 가격 정책 변경할때 마다 이 클래스를 수정해줘야 함...탈락

DIP -> 변하지 않을 클래스(추상클래스) 관계 맺지 않음..아주 구체적인 할인 클래스와(FixDiscountPolicy) 관계 맺음...탈락

 

결론적으로는 이 클래스가 할인 정책을 변경하면 안됨! 

이 클래스는 할인정책을 뭘 선택 하든 상관 없고 뭐가 들어올지도 몰라야 한다.

그래서 구현 클래스를 연결해주고 선택하는 설정 클래스를 만들어주어야 함!!

 

4. 해결

AppConfig

public class AppConfig {
    public MemberService memberService(){
        return  new MemberServiceImpl(memberRepository());
    }

    private MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    public DiscountPolicy discountPolicy(){
        return new RateDiscountPolicy();
    }
}

구현 클래스를 연결해주고 선택하는 설정 클래스

이 클래스를 통해 OrderService를 이용한다.

OrderService를 이용할때 생성자를 통해 내가 무슨 정책을 사용할것인가!!! 를 선택해서 보내줌!

정책 변경을 수정할 일이 있다? 그럼 AppConfig 수정하면 됨

 

OrderServiceImpl

public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

    @Override
    public Order crateOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

수정된 OrderService를 보자

생성자를 통해 구체적인 회원 저장소와 할인 정책을 가지고 온다.

여기에서 할인정책을 뭘 쓸껀지 정하지 않는다 무슨 정책을가지고 오던지 다 ㄱㅊ

 

아까 문제점이였던 SOLID 할인 정책을 다시 생각해보자

 

SRP -> 주문만 해준다 주문 외에 해야 할일? 없음 

OCP -> 수정에도 닫혀있다. 할인 정책변경할일이 있으면 이 클래스에서 수정하지 않고 AppConfig에서 수정함

DIP -> 인터페이스 (memeberRepository, discountPolicy)와 연결되어 있음 구체적인 RateDiscount, FixDiscount와 연결되어 있지 않음

 

5. 테스트

public class OrderServiceTest {

    MemberService memberService;
    OrderService orderService;

    @BeforeEach //테스트 하기 전에 실행 함
    public void beforeEach(){
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
        orderService = appConfig.orderService();

    }

    @Test
    void createOrder() {
        Long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberService.join(member);

        Order order = orderService.crateOrder(memberId, "ItemA", 1000);

        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }
}

Junit을 사용하여 테스트 해보았다

Appconfig가 무슨 정책을 사용할지 회원레파지토리를 뭘 사용할지 정하기 때문에

memberService와 orderService는 Appcofig를 통해 만들어야 한다!

통과 완

 

출저 : 인프런 스츠링의 핵심원리 - 기본편 (김영한)

'Java' 카테고리의 다른 글

[JAVA] Collection(List, Set)  (0) 2023.02.13
toLowerCase(), to UpperCase(), isUpperCase(), isLowerCase()  (0) 2023.01.01
String to CharArray  (1) 2023.01.01