반응형
1. 그냥 java 코드를 사용할 때
- 객체 지향의 원리에 맞게, 역할과 구현을 나누어 분리해봅시다.
회원 가입을 위한 요구사항을 만족하는 프로그램을 먼저 작성해봅시다!
회원 도메인
도메인 간 협력관계 : 기획자와 개발자가 모두 합의하에 참고하는 문서입니다.
클래스 설계 : 개발자가 전체적인 클래스를 설계합니다.
public interface MemberService {
void join(Member meber);
Member findById(int id);
}
public class MemberServiceImpl implements MemberService {
private MemberRepository memberRepository = new MemoryMemberRepository(); //실제 구현체를 지정. @Override public void join(Member member){ this.memberRepository.save(member); } @Override public Member findById(Long id){
return this.memberRepository.findById(id);
}
}
실제 클래스 간 의존관계 : 실제 의존관계에 있는 구현체를 보여줍니다.
빨간색 구현체끼리는 서로 갈아낄 수 있도록 '역할’을 부여하고, 서로 '역할’에만 의존하고 구현체에는 의존하지 않도록 하여, 쉽게 갈아끼울 수 있습니다.
public class MemberServiceImpl implements MemberService {
private MemberRepository memberRepository = new MemoryMemberRepository(); //실제 구현체를 지정
//....
}
2. 문제점
SOLID 원칙에 부합하는가?
실제로 갈아끼우려고 할 때 일어나는 일
public class MemberServiceImpl implements MemberService {
//private MemberRepository memberRepository = new MemoryMemberRepository();
private MemberRepository memberRepository = new DataMemberRepository();
//실제 구현체를 지정하는 부분 코드를 수정
}
- SOLID 원칙
- SRP : Single Responsility - 하나의 책임만 갖는가? -> ✅
- 하나의 책임만 가지기 때문에 바로 갈아 끼울 수 있습니다. (다형성)
- OCP : Open/Close - 확장에는 열려있고 변경에는 닫혀있는가? ❌
- 확장이 쉬운 것은 사실이나, 실제 코드를 변경해야 한다는 점이 문제입니다!!
- OCP 위반!
- LSP - 객체는 인터페이스 규칙을 지키는가? -> ✅
- ISP - 여러 인터페이스로 구성되는 것이 복잡한 범용 인터페이스보다 낫다. - ✅ (해당없음)
- DIP - 추상화에만 의존하고, 구체화에는 의존하지 말아야 한다. ❌
- 자료형 MemberRepository 는 추상화라서 추상황에 의존하지만, 실제 구현체 MemoryMemberRepository, DataMemberRepository를 각각 다르게 대입해야한다는 점에서 구현체에도 의존하고 있습니다.
- DIP 위반!
- SRP : Single Responsility - 하나의 책임만 갖는가? -> ✅
3. 문제점 해결
- 뮤지컬에서 남배우, 여배우의 역할이 있다면, 해당 역할을 하는 배우들은 각자의 역할만 잘 하면 될 뿐, 상대 배우에 대해 걱정할 필요가 없습니다.
- 하지만 위의 문제점은, 남배우가 직접 여배우를 고르고, 여배우가 직접 남배우를 골라야 하는 문제점과 동일합니다.
- 따라서 배우를 지정해주는 또다른 역할을 도입하여 이런 문제를 해결할 수 있습니다.
public class MemberServiceImpl implements MemberService{
private MemberRepository memberRepository;
//인터페이스, 즉 역할에만 의존
public MemberServiceImpl(MemberRepository memberRepository){
//생성자를 통해 인자로 받은 참조값이 곧 구현체.
//생성자의 인자를 통해 의존관계에 있는 구현체를 주입받는다.
this.memberRepository = memberRepository;
}
@Override public void join(Member member){
this.memberRepository.save(member);
}
@Override public Member findById(Long id){
return this.memberRepository.findById(id);
}
}
public class AppConfig{
public MemberService(){
return new MemberServiceImpl(new MemoryMemberRepository());
}
}
public class App {
public static void main(String[] args){
AppConfig appconfig = new Appconfig();
MemberService memberService = appconfig.MemberService();
}
}
- 변화된 점
- MemberServiceImpl 서비스가 MemberReposiotry 인터페이스(역할)에만 의존할 뿐, 실제 어떤 구현체인지 상관하지 않아도 됩니다.
- 구현체를 직접 설정해주는 것은 AppConfig 클래스에서 지정해줍니다.
- main에서 MemberService 는 어떤 구현체인지 상관하지 않습니다.
- 역시 구현체를 직접 설정해주는 것은 AppConfig 클래스에서 지정해줍니다.
- 이후에 MemberSeriviceImpl 이라는 클래스 대신, 다른 MemberServiceSecond 를 구현하여 사용하고 싶다면,
- AppConfig 에서만 코드를 수정하면 됩니다.
- MemberServiceImpl 서비스가 MemberReposiotry 인터페이스(역할)에만 의존할 뿐, 실제 어떤 구현체인지 상관하지 않아도 됩니다.
public class AppConfig{
public MemberService(){
return new MemberServiceSecond(new MemoryMemberRepository());
}
}
public class App {
public static void main(String[] args){
AppConfig appconfig = new Appconfig();
MemberService memberService = appconfig.MemberService();
}
}
4. 사용 영역과 구성 영역의 분리
- 구현체가 변경될 경우, 구성 영역의 AppConfig 만 변경되면 됩니다.
- 사용 영역의 어떤 코드도 고려할 필요가 없는 것입니다.
5. 개념들
1. 제어의 역전 (IoC) - Inversion of Control
- 말그대로 제어권이 뒤바뀌는 것.
- AppConfig가 등장한 이후로, 구현 객체는 자신의 로직을 실행하는 역할만 담당합니다. 프로그램의 제어흐름은 AppConfig 가 담당하게 되는 것입니다.
- 인터페이스를 호출하지만 실제 어떤 객체가 실행될지 모르는 것.
- 이렇듯 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC) 라고 합니다.
2. 프레임워크 vs 라이브러리
- 프레임워크
- 내가 짠 코드 실행의 제어를 담당한다면, 프레임워크가 맞습니다. (JUnit)
- 라이브러리
- 내가 작성한 코드가 직접 제어 흐름을 담당하는 경우, 프레임워크가 아니라 라이브러리입니다.
3. 의존 관계 주입
- 정적인 의존관계 vs 동적인 의존관계
- 정적인 의존관계
- 실행하지 않아도 코드, import 문을 보고 분석할 수 있습니다. (클래스 다이어그램)
- 동적인 의존관계
- 동적인 객체 인스턴스 의존 관계
- 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계.
- 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것 = 의존관계 주입(의존성 주입)
- 의존관계 주입 사용시, 클라이언트 코드 변경 없이, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있습니다.
- 의존 관계 주입을 사용하면, 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 변경할 수 있습니다.
4. IoC 컨테이너, DI 컨테이너
- AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것.
- IoC 컨테이너(제어권이 넘어가는. 범용적인.) -> DI 컨테이너라고 불립니다다.
- 어셈블러, 오브젝트 팩토리라고 불리기도 합니다.
반응형
'개발 > java' 카테고리의 다른 글
[Spring 기본] 4. 싱글톤 컨테이너 (0) | 2024.01.06 |
---|---|
[Spring 기본] 3. Spring 프레임워크 사용하기 (1) | 2023.12.21 |
Input 찬찬히 뜯어보기. (BufferedReader와 InputStreamReader?) (0) | 2023.12.11 |
[Spring 기본] 1. 객체 지향 설계와 스프링 (4) | 2023.12.08 |
JUnit (0) | 2023.11.03 |