개발/java

[Spring 기본] 2. 실제 코드에서의 스프링 프레임워크 핵심 원리

ebang 2023. 12. 8. 22:00
반응형

1. 그냥 java 코드를 사용할 때

  • 객체 지향의 원리에 맞게, 역할과 구현을 나누어 분리해봅시다. 

 

회원 가입을 위한 요구사항을 만족하는 프로그램을 먼저 작성해봅시다!

 

회원 도메인

 

 

도메인 간 협력관계 : 기획자와 개발자가 모두 합의하에 참고하는 문서입니다. 

 

클래스 설계 : 개발자가 전체적인 클래스를 설계합니다. 

xGEtOSE.png

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 위반!

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 에서만 코드를 수정하면 됩니다.
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 컨테이너라고 불립니다다.
  • 어셈블러, 오브젝트 팩토리라고 불리기도 합니다. 
반응형