차곡차곡 성 쌓기

[스프링 핵심원리 - 기본편 (김영한)] 강의를 수강 후 복습하는 글입니다😀

 

1. SRP 단일 책임원칙

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

 

수정 전 

package hello.core.member;

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

 

현재 'MemberSrviceImple`은 객체 생성, 연결, 실행을 하는 다양한 책임을 가지고 있다.

=> SRP 위반

 

객체 생성, 연결을 다른 객체로 책임을 넘겨보자

 

수정 후

package hello.core.member;

public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository){
        this.memberRepository = memberRepository;
    }
    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

=> SRP, DIP 충족

 

의존 관계였던 `MemberRepository`를 외부에서 주입받으므로써, 객체 생성과 연결의 책임이 없어졌다. 현재의 'MemberSrviceImple`은 오로지 실행에만 집중할 수 있다.

 

또한 코드 어디서도 구현체를 사용하는 코드가 없다. 오로지 추상회에 의존하며 DIP원칙을 충족한다. 이로써 새로운 `MemberRepository`가 추가되더라도 해당 클래스는 코드 변경없이 새로운 저장소를 받아들일 수 있다.

 

이를 위해 '구현 객체를 생성'하고 '연결'하는 책임을 가지는 별도의 설정 클래스를 만든다! 애플리케이션의 전체 동작을 구성하기 위한 클래스이다.

package hello.core;

/*
    애플리케이션의 전체 동작은 구성하기 위해 '구현 객체를 생성'하고 '연결'하는 책임을 가지는
    별도의 설정 클래스
 */
@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService(){
        // 이로써 MemberServiceImpl은 구현체가 바뀌어도 코드 수정 필요가 없음 => DIP 원칙 지킴
        return new MemberServiceImpl(getMemberRepository());
    }

    // 2개로 중복되어서 2번 고쳤어야 했는데, 여기만 고치면 됨
    @Bean
    private static MemoryMemberRepository getMemberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(getMemberRepository(), discountPolicy());
    }

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

 

 

2. DIP 의존 관계 역전 원칙

"프로그래머는 추상화에 의존하고 구현체에 의존하면 안된다."

 

수정 전 

package hello.core.member;

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

구현체인 `MemoryMeberRepository`에 의존하고 있다. 언뜻 추상화에 의존하고 있는 것처럼 보이지만 사실 추상화, 구현체 둘 다 의존하고 있는 상황이다. 

 

추상화에만 의존하도록 해보자.

 

수정 후

package hello.core.member;

public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository){
        this.memberRepository = memberRepository;
    }
    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

 

구현체 코드를 없앤다. 그러면 널포인터 에러가 난다. 누군가 생성해줘야 한다! 위에서 언급했던 AppCofing에서 객체를 생성 후 주입한다.

객체를 주입받기 위해선 `MemberServiceImpl`은 추상화에만 의존하고 있어야한다.

 

만약 구현체에 의존하고 있으면 변동사항이 생길 때 `MemberServiceImpl`코드도 수정하고 `AppCofing` 코드도 수정해야 한다. 오로지 추상화 인터페이스에 의존하고 있어야만 클라이언트의 코드 수정없이 새로운 사항을 추가할 수 있다. 

 

3. OCP

"확장에는 열여있고 변경에는 닫혀있어야 한다.

 

다형성을 사용하고 DIP원칙을 지키면 된다.

public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

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

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        // 회원 정보 조회
        Member member = memberRepository.findById(memberId);
        // 할인정책에게 할인 역할 넘기기 -> 단일 책임 원칙 잘 준수됨.
        int discountPrice = discountPolicy.discount(member, itemPrice);

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

 

AppCofing가 객체를 생성 후 주입하기 때문에, 'OrderServiceImpl'은 아무런 코드 변경없이 실행될 수 있다. 이때 변경하는 것은 AppConfig의 `discountPolicy`의 객체 생성 코드이다. 

package hello.core;

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService(){
    
    //....
    
    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(getMemberRepository(), discountPolicy());
    }

    @Bean
    public DiscountPolicy discountPolicy(){
        return new RateDiscountPolicy();
    }
}
728x90

'스프링 부트' 카테고리의 다른 글

[Spring] h2적용 후 postman으로 테스트하기  (0) 2024.02.22
스프링 : 빈 등록하기  (0) 2023.12.14
profile

차곡차곡 성 쌓기

@nagrang

포스팅이 좋았다면 "좋아요" 해주세요!