SOLID원칙은 참 어려운 원칙이다.
정답이 존재하지 않고,
또 더 나아지는 코드를 만들 방법은 무한하기 때문이다.
우리는 SOLID원칙을 잘 준수하기 위해
공통 모듈은 분리하고,
확장은 용이하게,
가독성 또한 좋게,
등등의 방법을 사용한다.
그 여러 방법중에서
적절하게 분리를 잘 시키기 의 방법도 있다.
잘 분리된 정말 쉬운 예시는
다음과 같다.
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) {
memberRepository.save(member);
}
}
MemberServiceImpl
클래스에서는,
join
매서드를 통해 멤버를 저장한다.
근데 다시한번 자세히 봐보자.
memberRepository.save(member);
로직을 통해 저정한다.
즉, 저장의 수행은 memberRepository
가 행하는것이다.
즉 각자는 각자가 수행해야 할 일만 잘 해내고,
그렇게 수행해야 할 일은 철저하게 분리시켜야 한다.
그런데! 여기서,
각자가 할 일을 분리해주면, 일이 세분화되고,
클래스에서 또다른 클래스의 일이 필요할 때가 생긴다.
우리는, 이때 의존성을 주입(DI)받아 사용한다.
//여러가지 의존성 주입 방법 중 하나인 생성자 주입
//(Service 단위에서 할 일이 repository 클래스의 역할이 필요하기 때문에)
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
그리고, 의존성을 주입받는 방법은 여러가지가 있다.
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
흔히 볼 수 있고, 가장 많이 사용하는 방식이다!
해당 코드가 생성자 주입 방식이다.
@Component
어노테이션때문에
MemberServiceImpl
이 스프링 컨테이너에 등록될때,
Spring에서 @Autowired
어노테이션을 보고
MemberRepository
를 주입해준다.
위와 같이 MemberServiceImpl
에 memberRepository
를 주입해주면,
그 누구도 MemberServiceImpl
클래스의 필드변수 memberRepository
를 수정할 수 없다.
다시 말하면,
memberRepository
)을 변경할 필요가 없을때.final
생성자로 해당 값은 할당시켜주어야 할때.참고 : 생성자가 위의 코드처럼 하나라면, @Autowired
없이도 스프링이 알아서 해당 생성자를 선택해서 사용한다.
(위의 코드에선 @Autowired
없어도 된다는 소리)
@Component
public class MemberServiceImpl implements MemberService {
private MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
우리가 흔히 사용하는 setter 매서드를 이용해서
의존관계를 주입한다.
public void setMemberRepository
와
final
키워드가 없는것을 확인할 수 있다.
즉,
내가 원하는 값(원하는 memberReopsotory
)을 선택해서 할당 가능하다.
memberRepository
값은 불변이 아니라, setMemberRepository
를 통해 변경 가능하다.
@Component
public class MemberServiceImpl implements MemberService {
@Autowired
private MemberRepository memberRepository;
@Override
public void join(Member member) {
memberRepository.save(member);
}
}
필드변수에 의존관계를 바로 때려박아버리는 방식이다.
코드가 간결해져서 어.. 이거 괜찮을지도...? 라는 생각이 들 수 있다.
하지만, memberRepository
필드를 Spring에서 내부적으로 정해서 할당해주기 때문에,
이 방식은 테스트하기 힘들다는 치명적 단점이 있다.
위의 필드 주입 방식으로 작성하고 나서,
테스트가 실패하는 내용을 코드로 예시를 보자.
@Test
void fieldInjectionTest() {
//given
MemberServiceImpl memberService = new MemberServiceImpl();
Member testMember = new Member(1L, "테스트멤버", Grade.VIP);
//when
memberService.join(testMember);
//then
}
해당 코드는, Spring 없이 순수 자바 코드로 작성되었다.
필드 주입 방식은,
필드변수에 바로 @Autowired
어노테이션을 사용하면
우리가 Spring 프레임워크를 사용하여 어플리케이션을 구동하면
자동으로 memberRepository
를 찾아서 주입해주지만,
순수 자바에서는 그렇지 않다.
즉, join
매서드에서,
memberRepository.save()
를 실행시킬때,
주입된 memberRepository
가 없으므로,
테스트코드의 memberService.join(testMember);
에서 아래와 같이
NullPointerException
예외가 발생하게 된다.
의존성을 주입해주는 프레임워크가 없다면 위와 같이 복잡한 상황이 나올 수 있으므로, 거의 사용하지 않는다.
@Component
public class MemberServiceImpl implements MemberService {
private MemberRepository memberRepository;
@Autowired
public void init(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
init()
이라는 일반 매서드를 통해 주입하는 방식이다.
근데, 생성자 주입과 별반 다를게 없다.
서로 다른점은 매서드, 생성자 정도라는것이다.
그렇기 때문에, 많이 사용되는 방식이 아니다.
레퍼런스 : 김영한님 Spring 강의