멈재

[JAVA/Spring] 04. DI & IoC & Bean - 1 본문

JAVA & Spring & JPA

[JAVA/Spring] 04. DI & IoC & Bean - 1

멈재 2022. 10. 9. 17:42
728x90

0. DI? IoC? Bean?

Spring프레임워크에서 빼놓을 수 없는 용어이고 처음 입문하는 사람에게는 이만큼 어려운 게 없을 것이다.

의존성 주입...? 빈..? 스프링 컨테이너..?
너무 어려운데 그냥 new로 생성하면 안 되나...?

내가 처음 Spring을 접했을 때 가졌던 물음표였다.

 

그래서 내가 누군가에게 DI & IoC & Bean에 대해 설명할 기회가 생긴다면 '왜' 이것이 핵심 원리가 된 것이고 사람들이 열광하는지에 대한 근본적인 원인부터 설명해줘야겠다는 생각을 했다.

이번 1편에서는 어떤 문제가 있는지 알아보고, 2편에서 DI & IoC & Bean에 대해 다뤄볼 생각이다.

 

1. 다형성

2. SOLID 원칙

3. 싱글톤 패턴

 

JAVA를 한 번쯤은 시작해본 사람은 다형성과 SOLID 원칙에 대해 들어봤을 것이다.

 

0-1. 다형성

다형성(Polymorphism)은 간단히 말하면 자바에서 부모 클래스의 타입의 참조 변수로 자식 클래스의 인스턴스를 참조할 수 있음을 의미한다. 약간의 설명을 더하면, 부모-자식 상속 관계에서 부모 클래스가 동일한 호출로 자식 클래스들을 다르게 동작시키는 객체 지향 원리라고 할 수 있겠다.

 

자세한 건 구글링을 해보거나 티스토리 내 다형성을 참고해보자.

https://jay-ya.tistory.com/6

 

[JAVA] 02. 다형성(Polymorphism)

객체지향개념에서 다형성이란 여러 가지 형태를 가질 수 있는 능력을 의미하는데, 자바에서는 부모클래스의 타입의 참조변수로 자식클래스의 인스턴스를 참조할 수 있음을 의미한다. 너무 추

jay-ya.tistory.com

 

다음으로 SOLID 원칙이다. 이 원칙이 무엇인지 알고 이해하게 되는 순간이 객체 지향의 매력을 알게 되는 순간이라고 생각한다.

SOLID란 로버트 마틴이 명명한 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙을 소개한 것. 프로그래머가 시간이 지나도 유지 보수와 확장이 쉬운 시스템을 만들고자 할 때 이 원칙들을 함께 적용할 수 있다. (WIKI)

Q. 그렇다면 좋은 객체 지향 설계는 무엇일까?
A. 유연하고 변경에 용이하도록 설계된 것이라고 할 수 있다. 그리고 이것을 가능케 하는 것이 '다형성'의 개념이다.

 

그래서 SOLID는 좋은 객체 지향 설계를 하기 위한 원칙이라고 할 수 있다.

 

 

0-2. SOLID란

SRP 단일 책임 원칙 (Single responsibility principle) 클래스는 하나의 책임만 가져야 한다.
OCP 개방-폐쇄 원칙 (Open/closed principle)“소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.”
LSP 리스코프 치환 원칙 (Liskov substitution principle)“프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.” 계약에 의한 설계를 참고하라.
ISP 인터페이스 분리 원칙 (Interface segregation principle)“특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.”[4]
DIP 의존관계 역전 원칙 (Dependency inversion principle)

출처: 위키백과_SOLID(객체 지향 설계)

 

 

각각에 대해 조금만 더 알아보자.

 

1. 단일 책임 원칙 (SRP, Single Responsibility Principle)

  • 한 클래스는 하나의 책임만 가져야 한다.
  • 중요한 기준은 변경이다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것.
  • ex. UI 변경, 객체의 생성과 사용을 분리

 

 

2. 개방 폐쇄 원칙 (OCP, Open-Closed Principle)

  • 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다.
  • 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현한다.
  • 다형성을 생각해보자.
    • 기존에 기아 K시리즈의 인터페이스를 상속받는 K3 만 존재했지만 K5가 새로 생성된다면 단순히 인터페이스를 상속받아서 구현만 하면 되기 때문에 확장에는 열려 있으나 변경에는 닫혀있다. 는 것처럼 보인다. 즉, 개방 폐쇄 원칙가 잘 적용된 것 같다.
    • 그러나 차를 생산(구현)하려고 한다면 K3를 할지 K5를 할지. 즉, 구현 클래스를 선택하는 과정에서 코드의 변경이 이루어져야 한다. 다시 말해, 다형성만으로는 OCP 원칙을 지킬 수 없다.
public class KiaCarService {
    // 변경전
     private KiaCar kiaCar = new k3();

    // 변경후
//	private KiaCar kiaCar = new k3();
    private KiaCar kiaCar = new k5(); // ??
}
Q> 그렇다면 이런 문제를 어떻게 해결할 수 있는가?
A> 객체를 생성하고 연관관계를 맺어주는 별도의 조립해주고 설정해주는 중간자 역할을 해주는 것이 필요하다.

 

 

3. 리스코프 치환 원칙 (LSP, Liskov substitution principle)

  • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  • 다시 말해, 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것이다.
  • 다형성을 지원하기 위한 원칙으로 인터페이스를 구현한 구현체를 믿고 사용하려면 이 원칙을 준수해야 한다. 단순히 컴파일에 성공하는 것만을 이야기하는 것이 아닌 더 나아가서
    • ex. 자동차 인터페이스의 브레이크는 멈추는 기능이다. 만약 속도가 나도록 구현하면 리스코프 치환 원칙에 위배된다. 느리거나 급정거하더라도 멈추어야 한다.

 

 

4. 인터페이스 분리 원칙 (ISP, Interface Segregation Principle)

  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
  • ex.
    • 자동차 인터페이스 -> 운전 인터페이스, 정비 인터페이스로 분리
    • 사용자 클라이언트 -> 운전자 클라이언트, 정비사 클라이언트로 분리
  • 이렇게 분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않는다. 또한, 인터페이스가 명확해지고 대체 가능성이 높아진다.

 

5. 의존관계 역전 원칙 (DIP, Dependency Inversion Principle)

  • 프로그래머는 “추상화에 의존해야지 구체화에 의존하면 안 된다.”의 원칙으로 의존성 주입은 이 원칙을 따르는 방법 중 하나다.
  • 다시 말해, 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 의미이다.
  • 앞서 설명한 역할(Role)에 의존하게 해야 한다는 것과 일맥상통하다. 객체 세상도 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다. 만약, 구현체에 의존하게 되면 변경이 어려워진다.
    • 운전자는 자동차 역할(엑셀을 밟으면 나간다. 브레이크를 밟으면 멈춘다 등과 같은)에 대해서만 알면 되는 거지 굳이 K3나 아반떼의 구현이 중요한 게 아니다.
    • 그런데 OCP에서 설명한 MemberService는 인터페이스에 의존하지만 구현 클래스에도 의존 한다.
여기서 말하는 의존한다 는 알고 있다와 일맥상통한다. 
즉, 코드에 대한 변경이 이루어지려면 관련된 코드를 알고 있어야 한다는 의미이다.
쉽게 말해, 클라이언트(MemberService)가 구현 클래스를 직접 선택해야 한다.
private KiaCar kiaCar = new k3();
private KiaCar kiaCar = new k5(); // 변경

 

객체 지향의 핵심은 다형성이다.

그러나, 다형성만으론 추상화에 의존하고 구체화에 의존하는 것이 불가능하고, 다형성만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경된다.

즉, 다형성만으로는 OCP, DIP를 준수할 수 없다.

 

 

0-3. 싱글톤 패턴(Singleton Pattern)

마지막으로 GoF 디자인 패턴 중 생성(Creational) 패턴에 속하는 것으로 싱글톤 패턴이다.

디자인 패턴은 설계 문제에 대한 해답을 문서화하기 위해 고안된 형식 방법이다. (WIKI)
쉽게 말하면, 소프트웨어를 설계할 때 빈번하게 발생하는 문제들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 정리한 것을 의미한다.

 

100번 양보해서 다형성과 SOLID 원칙까지 이해하겠는데 뜬금없이 싱글톤 패턴......?이라는 생각이 들 수도 있다.

 

그. 런. 데

 

이 싱글톤 패턴이 스프링 프레임워크가 적용하는 패턴이라면?? 🤝😏

 

싱글톤 패턴의 정의는 단순하다. 객체의 인스턴스가 오직 1개만 생성되는 패턴. 끝.

다시 말해 인스턴스가 오직 1개만 생성되기 때문에 애플리케이션이 실행되면 최초 한 번만 메모리를 할당한다.

이게 어떻게 가능한 것인가?

 

싱글톤을 코드로 구현하면 다음과 같다.

public class Singleton {
    private static Singleton instance = new Singleton();
    
    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
	...
}

싱글톤 패턴은 두 가지 조건이 필요하다.

  1. private생성자로 외부에서 임의로 new키워드를 사용하지 못하도록 막아야 한다.
  2. 해당 객체가 필요한 경우 객체 instance를 반환받을 수 있는 메서드를 호출한다.

 

이런 싱글톤 패턴은 장단점이 명확하다.

장점

최초 한 번만 new연산자에 의해 하나의 인스턴스만 생성되기 때문에 메모리 낭비를 방지할 수 있다.

싱글톤 인스턴스는 전역적으로 사용 가능한 인스턴스이기 때문에 데이터 공유가 쉽다.

 

단점

여러 클래스의 인스턴스에서 사용 가능하기 때문에 동시성 문제가 발생할 수 있다.

의존 관계상 클라이언트가 구체 클래스에 의존하게 된다.

 

 

사실 싱글톤 패턴은 안티 패턴이라고 불릴 객체 지향에 위반되는 사례가 많다. 그리고 장점보다 단점이 크다고 생각된다.

 

근데,,,, 스프링 프레임워크가 가지는 기능으로 싱글톤 패턴이 가지는 문제점을 보완하면서 사용할 수 있다.

 


 

 

왜 다형성, SOLID 원칙, 싱글톤에 대해 구구절절 얘기하나라고 생각할 수 있다.

사실 이것을 말해주기 위해 이만큼을 설명한 것이다.

다형성만으로는 SOLID 원칙을 완벽히 준수할 수 없다.

 

이제 이 문제들을 스프링 프레임워크가 어떻게 해결했는지 알게 보겠다.

 

 

2편

https://jay-ya.tistory.com/22

 

[JAVA/Spring] 05. DI & IoC & Bean - 2

애자일 소프트웨어 개발 선언 https://agilemanifesto.org/iso/ko/manifesto.html 애자일 소프트웨어 개발 선언 애자일 소프트웨어 개발 선언 우리는 소프트웨어를 개발하고, 또 다른 사람의 개발을 도와주면

jay-ya.tistory.com

 

 


[참고]

https://inf.run/BwG1

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com