객체 지향 원리 적용

naeganugu·2022년 7월 17일
0

스프링 마스터🌱

목록 보기
8/19

1) 객체 주입의 필요성

이전 포스트에서 말했듯이 앞선 코드의 문제점은 클라이언트 코드에서 new로 구체 클래스에 의존하고 있기 때문에, 결국 구현와 역할 모두에 의존하는 것이었다.

그래서 변경을 할 때 다음과 같은 문제가 생긴다.

public class OrderServiceImpl implements OrderService {
	// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}

우리는 클라이언트(OrderServiceImpl) 코드 변경 없이 수정과 변경에 용이하게 하고 싶은데, 결국 클라이언트 코드를 고쳐야하는 문제가 발생!

📍 다형성을 활용하고, 인터페이스(역할)와 구현 객체(구현)를 분리했다
📍 하지만 OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수하지는 못했다. 왜? 인터페이스뿐만 아니라 구체 클래스에도 의존하고 있기 때문이다.DiscountPolicy가 아니라 FixDiscountPolicy, RateDiscountPolicy까지도 의존하고 있다. 또 기능을 변경할 때 클라이언트 코드가 영향을 받는다.

그러면 어떻게 인터페이스에만 의존하도록 바꿀 수 있을까?
📌 쉽게 말해서 new로 클라이언트 코드 내에서 구체 클래스 객체를 생성하지 않으면 된다.

public class OrderServiceImpl implements OrderService {
	//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
	private DiscountPolicy discountPolicy;
}

이렇게 하면 인터페이스에만 의존할 수 있다. 그런데 문제,,, 구현체가 없는데 코드가 어떻게 동작하냐는 것이다.

저거를 discountPolicy. 해서 쓰는 순간 null pointer exception이 터질 것💥

이를 해결하려면 외부에서 클라이언트(OrderServiceImpl)에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해줘야 한다.

2) AppConfig 등장

위에서 나왔던 OrderServiceImpl 코드는 다양한 책임을 가지고 있다. 직접 구현 객체도 생성해줘야하고, 원래 목적에 맞게 order service에 관한 로직도 수행해야 한다. 다양한 책임을 분리해야 한다.

우리는 AppConfig 파일을 만들어서 구현 객체를 생성하고, 연결하는 책임을 AppConfig에 몰아준다. 애플리케이션의 전체 동작 방식을 구성하기 위해 만든다.

이 AppConfig에서 MemberServiceImpl, MemoryMemberRepository, OrderServiceImpl, FixDiscountPolicy 등 실제 동작에 필요한 구현 객체를 생성한다.

그리고 AppConfig는 생성한 객체를 생성자를 통해서 주입해준다.

AppConfig

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

📍AppConfig에서 실제 동작에 필요한 구현 객체를 생성한다.

public class MemberServiceImpl implements MemberService {
	private final MemberRepository memberRepository;
	public MemberServiceImpl(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}
...

📍클라이언트에서는 생성자를 통해 주입 받는다.

수정한 클라이언트 코드는 다음과 같다.

public class MemberServiceImpl implements MemberService{

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

}

이렇게 되면 MemberRepository 인터페이스에만 의존한다.

📌 MemberServiceImpl 입장에서는 생성자를 통해 어떤 구현 객체가 들어올지 알 수 없다.(MemoryMemberRepository일지 아니면 다른 구현체일지)
📌 오직 AppConfig에서 결정된다.
📌 이제 MemberServiceImpl은 실행에만 집중하면 된다.
📌 클라이언트 입장에서 의존관계를 외부에서 주입해주는 걸,DI(Dependency Injection) 의존관계 주입 또는 의존성 주입이라고 한다.

DI(Dependency Injection): 의존관계 주입, 의존성 주입

3) AppConfig 리팩터링

  • 이전 AppConfig
public class AppConfig {
	public MemberService memberService() {
		return new MemberServiceImpl(new MemoryMemberRepository());
	}
	public OrderService orderService() {
		return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
	} 
}

중복이 있고, 역할에 따른 구현이 잘 보이지 않는다.

  • 리팩터링 후
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();
	}
 
 }

new 하는 중복이 제거되었기 때문에, 만약 할인 정책을 바꾼다면 discountPolicy 부분만 바꾸면 된다.



[출처]

스프링 핵심 원리-기본편

profile
seungseung-zanggu

0개의 댓글