개요
토비의 스프링을 다시 읽으며, 개념을 자세히 정리하기보다는 면접에 나올 만한 부분을 질의응답 형식으로 정리하려고 합니다.
책을 읽는 동안 지속적으로 업데이트되는 게시글입니다.
친절하게 설명을 해주시기 위해 분량이 많아진 것이라 생각하기 때문에, 입문자도 한 번 정도는 읽어보는 것도 좋다고 생각합니다.
내용의 순서는 가나다 순이 아닌 책의 흐름에 따라 진행됩니다.
전체 글 목록은 아래 페이지에서 확인할 수 있습니다.
질문 및 답변
1권의 1장인 오브젝트와 의존관계 챕터에 대한 질답 목록입니다.
객체지향의 주요 개념과 디자인 패턴 그리고 IoC와 DI의 필요성과 원리 등에 대한 질답입니다.
관심사의 분리 원칙(Separation of Concerns, SoC)
변화는 보통 집중된 한 가지 관심에 대해 일어나지만 그에 따른 작업은 한곳에 집중되지 않는 경우가 많다. 관심사의 분리는 관심이 같은 것끼리 하나의 객체 또는 가까운 객체로 모아, 관심이 다른 것과는 가능한 떨어져 서로 영향을 주지 않도록 분리하는 것이다. 이를 통해 변경이 일어날 때 필요한 작업을 최소화할 수 있고, 그러한 변경이 다른 곳에 문제를 일으키지 않게 할 수 있다.
리팩토링
기존의 코드를 기능의 동작방식에는 변화 없이 내부 구조만 변경하여 재구성하는 작업으로, 코드의 내부 설계를 개선하여 코드를 이해하기 쉽게 만들고, 변화에 대응하기 쉽게 할 수 있다. 이를 통해 코드의 품질과 생산성을 향상하고, 유지보수가 편리해지는 견고하면서도 유연한 개발을 할 수 있다.
메서드 추출
공통의 기능을 담당하는 메서드로 중복된 코드를 추출하는 기법이다.
상속과 확장
상속을 통해 자식 클래스가 부모 클래스의 자원을 물려받아, 부모 클래스와 다른 부분만 추가하거나 재정의하여 기존 코드를 쉽게 확장할 수 있다. 하지만 이렇게 코드의 재사용만을 위한 기법이 아닌 더 구체적인 클래스를 구현하기 위해 사용하는 것이 주목적이다.
템플릿 메서드 패턴
상속을 통해 슈퍼 클래스의 기능을 확장할 때 사용하는 가장 대표적인 디자인 패턴이다. 변하지 않는 기능을 슈퍼 클래스에 구현하고 자주 변경되고 확장되는 기능은 서브 클래스에 만드는 방식으로, 이러한 부분을 추상 메서드 혹은 오버라이드가 가능한 메서드로 정의하여 템플릿처럼 사용한다.
팩토리 메서드 패턴
오브젝트를 만드는 팩토리 클래스를 사용하여 슈퍼 클래스는 구체적인 오브젝트에 대해서는 알지 못하게 하는 디자인 패턴이다. 클래스의 생성과 사용을 분리하여 결합도를 낮추고, 캡슐화를 통해 정보를 은닉 처리할 수 있다는 장점이 있다.
슈퍼 클래스는 오브젝트의 생성에 대해 추상화를 하고, 이를 서브 클래스가 어떤 오브젝트를 생성할지 결정하기 때문에 슈퍼 클래스는 추상화된 오브젝트만 알고 있게 된다.
디자인 패턴의 개념과 장점
소프트웨어 설계 시 특정 상황에서 자주 만나는 문제를 해결하기 위해 사용할 수 있는 재사용 가능한 설루션이다. 다양한 디자인 패턴에 대해 이해하고 있다면 문제를 마주치더라도 쉽게 효율적인 설루션을 생각할 수 있고, 자신과 상대가 모두 디자인 패턴에 대해 알고 있다면 의사소통을 더욱 간결하게 할 수 있다.
상속의 단점
자바는 다중 상속을 허용하지 않기 때문에 이미 상속을 받은 클래스에 추가적인 상속이 필요한 경우에는 상속을 적용하기 힘들며, 관심사를 분리하고 확장성이 유용하게 만들어도 상속을 통해 밀접한 관계가 될 수밖에 없다는 단점이 있다.
인터페이스를 사용하는 이유
인터페이스를 사용하면 객체 입장에서는 자신이 사용하는 클래스에 대해 직접적으로 알지 못하게 된다. 즉, 인터페이스는 사용하는 클래스와 실제 사용되는 클래스의 사이에서 두 클래스의 긴밀한 관계를 느슨하게 해 준다. 결국 사용하는 입장에서는 인터페이스에 정의된 기능에만 관심을 가지기 때문에 실제로 구현된 내용에는 관심을 가지지 않게 된다. 따라서 사용하고자 하는 클래스가 바뀌어도 아무런 문제가 생기지 않는다.
의존관계를 직접 설정하지 않는 이유
객체 자신이 사용할 오브젝트를 직접 생성하고 선택하는 것도 하나의 관심사라고 볼 수 있다. 이러한 관심사를 분리하기 위해 객체 자신을 사용하는 클라이언트 객체에 위임하여 런타임 시에 오브젝트와 오브젝트 사이의 의존(사용) 관계를 설정하게 한다.
개방 폐쇄 원칙(OCP : Open-Closed Principle)
확장에 대해서는 개방적이고 수정에 대해서는 폐쇄적이어야 한다는 원칙으로, 기존의 코드에 영향을 주지 않고 기능을 추가할 수 있도록 설계해야 하는 것을 의미한다. 예를 들면 인터페이스를 통해 기능에 대한 확장은 개방하고, 이를 이용하는 클래스는 변화에 영향을 받지 않게 폐쇄할 수 있다.
즉, 변화의 특성이 다른 부분을 구분해주고, 각각 다른 목적과 다른 이유에 의해 다른 시점에 독립적으로 변경될 수 있는 효율적인 구조를 만들어주는 것이다.
객체지향 설계 원칙 (SOLID)
SRP(Single Responsibility Principle:단일 책임 원칙), OCP(Open-Closed Principle:개방 폐쇄 원칙), LSP(Liskov substitution principle:리스코프 치환 원칙), ISP((Interface segregation principle:인터페이스 분리 원칙), DIP(Dependency inversion principle:의존관계 역전 원칙) 총 5가지의 객체지향 설계 원칙을 의미한다. 특정한 문제에 대한 솔루션인 디자인 패턴과 다르게 더 일반적인 상황에서 적용 가능한 객체지향 설계의 기준이라고 볼 수 있다.
높은 응집도와 낮은 결합도
응집도는 하나의 모듈 혹은 클래스에 있는 기능들의 연관도로, 응집도가 높다면 하나의 관심사에만 집중되어 있기 때문에 변경이 필요할 때 특정 모듈만 수정하면 해결할 수 있다. 결합도는 관심이 다른 모듈 혹은 클래스와의 의존성의 정도로, 결합도가 낮다면 느슨하게 연결되어 있어 독립적이기 때문에 변경에 대한 여파가 전파되지 않는다.
전략 패턴
기능에서 변경이 필요한 알고리즘을 인터페이스로 분리시켜 이를 구현한 구체적인 알고리즘 클래스를 런타임시에 필요에 따라 바꿔서 적용할 수 있는 행위 디자인 패턴으로, 알고리즘의 변형이 빈번한 경우에 유용하다. 추상화된 전략 인터페이스와 이를 구현한 클래스, 전략을 등록하고 실행하는 컨텍스트와 전략을 설정하고 결과를 얻는 클라이언트가 있다.
스프링이란
객체지향적 설계와 디자인 패턴의 장점을 개발자들이 자연스럽게 활용할 수 있게 해주는 프레임워크다. IoC를 극한까지 적용하고 있는 IoC 프레임워크이기 때문에 컴포넌트의 생성과 관계설정, 사용, 생명주기 관리 등을 위해 애플리케이션 전반에 걸쳐 IoC를 적용하기 편리하게 도와준다.
오브젝트가 어떻게 설계되고, 만들어지고, 관계를 맺고 사용되는지에 관심을 맞춰 개발된 프레임워크다. 개발자가 이러한 과정들을 설계하고 작업할 때 반복적으로나 강제적으로 해야하는 번거로운 작업들을 편하게 할 수 있도록 지원해준다.
오브젝트 팩토리
객체의 생성 방법을 결정하고 만들어진 오브젝트를 돌려주는 오브젝트로 디자인 패턴에서 사용되는 팩토리와는 다르다. 단순하게 오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 분리하려는 목적으로 사용한다.
IoC(Inversion of Control)
오브젝트 스스로 자신이 사용할 클래스를 결정하고, 생성하는 것처럼 사용하는 쪽에서 제어하는 것이 아닌, 반대로 모든 제어 권한을 자신이 아닌 다른 오브젝트에게 위임하는 것이다. 이렇게 되면 오브젝트는 자신이 사용할 오브젝트를 스스로 결정하지 않고, 생성하지도 않으며, 자신도 어떻게 만들어지고 사용되는지 알 수 없다. 그래서 IoC를 적용하다 보면 자연스럽게 관심이 분리되고 책임이 나눠져 설계가 깔끔해지고, 유연성과 확장성이 향상된다.
추가로 프레임워크도 제어의 역전이 적용된 기술이라고 볼 수 있다. 개발자가 프레임워크를 사용하여 개발하는 것이 아닌 프레임워크에 맞춰 개발하기 때문이다.
스프링에서의 빈이란
스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해 주는 IoC가 적용된 오브젝트 단위의 컴포넌트이다. 스프링에서 사용된다고 모두 빈인 것이 아닌, 스프링 컨테이너에 의해 제어되는 것만을 빈이라고 한다.
빈 팩토리와 애플리케이션 컨텍스트의 차이점
빈 팩토리는 빈을 생성하고 관계를 설정하는 IoC의 기본적인 부분을 제어한다면 애플리케이션 컨텍스트는 애플리케이션 전반의 모든 구성요소의 제어 작업을 담당한다. 애플리케이션 컨텍스트는 BeanFactory 인터페이스를 상속했기 때문에 빈 팩토리라고 볼 수도 있다.
@Configuration
빈들을 애플리케이션 컨텍스트에 등록하기 위해 사용하는 애너테이션으로 클래스에 해당 어노테이션을 설정하면 해당 클래스의 본문을 파싱해 @Bean 애너테이션이 설정된 메서드의 이름으로 빈을 등록한다. 이때 등록된 빈은 스프링 컨테이너에 의해 관리되고 싱글톤임이 보장된다.
애플리케이션 컨텍스트의 개념과 동작방식 및 장점
애플리케이션 컨텍스트는 애플리케이션에서 IoC를 적용해 관리할 모든 오브젝트에 대한 생성과 관계설정 등을 담당한다. 애플리케이션 컨텍스트는 클라이언트가 빈을 요청하면 빈 목록에 해당 빈이 있는지 확인하여 빈을 생성하게 한 후에 클라이언트에게 전달한다. 이렇게 클라이언트는 구체적인 오브젝트의 생성 과정을 알 필요가 없기 때문에 일관된 방식으로 원하는 오브젝트를 가져올 수 있게 된다. 추가로 생성과 관계설정만이 아닌 오브젝트의 생성 방식, 시점, 전략, 후처리 등 다양한 기능을 제공하기도 한다.
동일성과 동등성
동일한 오브젝트라면 서로 같은 객체의 주소가 동일한 것이고, 동등한 오브젝트라면 서로 다른 객체지만 객체의 동등성 기준에 따라 동등한 객체인 것을 말한다.
스프링에서 싱글톤을 사용하는 이유
클라이언트의 요청마다 필요한 오브젝트를 새로 만든다면 서버에 부하가 생길 것이고 감당하기 힘들어 진다. 이러한 문제를 해결하기 위해 애플리케이션 안에 제한된 수의 오브젝트를 만들어서 사용하기 위한 것이 싱글톤 패턴의 원리이고, 따라서 스프링은 싱글톤을 사용한다.
싱글톤 패턴과 구현
클래스를 애플리케이션 내에서 제한된 인스턴스 개수나 보통 하나만 존재하도록 강제하는 디자인 패턴이다. 이렇게 생성된 인스턴스는 애플리케이션 내에서 전역적으로 접근이 가능하고, 주로 애플리케이션의 여러 곳에서 공유하는 경우에 사용된다.
클래스 내에서 오브젝트를 생성하지 못하게 생성자를 private으로 제한하고, 싱글톤 오브젝트를 저장할 스태틱 필드를 정의하여 팩토리 메서드를 사용해 한 번만 오브젝트를 생성하게 하여 필드에 저장한다. 그 후에는 이미 만들어져 스태틱 필드에 저장된 오브젝트를 넘겨줘 공유한다.
싱글톤 패턴의 한계
private 생성자 하나만 가지고 있기 때문에 다른 생성자가 없어 상속을 할 수 없고 다형성도 사용할 수 없다. 테스트에서도 만들어지는 방식이 제한되기 때문에 목 오브젝트 등으로 대체하기 힘들고, 자원을 공유하여 격리된 환경에서의 테스트가 힘들다. 또한, 클래스 로더가 두 개 이상일 경우 지정하지 않으면 싱글톤이 하나만 만들어지는 것이 보장되지 않고, 전역 상태로 사용되기 때문에 어떤 객체에서든 접근할 수 있다는 위험이 있다.
싱글톤 레지스트리
스프링에서의 싱글톤은 디자인 패턴의 싱글톤 패턴과 비슷하지만 구현 방법은 많이 다르다. 기존 싱글톤 패턴의 한계를 극복하기 위해 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 싱글톤 레지스트리 기능을 제공한다. 이는 오브젝트 생성에 대한 모든 권한이 애플리케이션 컨텍스트가 가지고 있기 때문에 싱글톤을 생성하고, 관리하고, 공급할 수 있기 때문에 가능한데, 이로 인해 스프링에서는 평범한 자바 클래스도 싱글톤으로 활용할 수 있다.
싱글톤을 사용할 때 주의해야 할 점
멀티 스레드 환경에서 하나의 싱글톤 오브젝트를 여러 스레드가 동시에 사용할 수 있기 때문에 내부에 상태를 저장하기 위해 읽기 전용이 아닌 인스턴스 변수를 수정하여 상태를 공유하는 것은 위험하다. 따라서 파라미터나 지역 변수, 리턴 값 등을 이용해 각 스레드마다 독립적인 공간을 활용해야 한다.
DI(Dependency Injection, 의존관계 주입)
오브젝트 레퍼런스를 외부로부터 주입 받아 이를 통해 다른 오브젝트와 런타임 시에 의존관계가 만들어지는 것이다.
의존관계
A가 B를 의존하고 있다는 것은 A의 의존대상인 B가 변하면 A에도 영향을 미친다는 것이고 반대로 B는 A에 의존하지 않고 있기 때문에 A의 변화는 B에 영향을 끼치지 않는다.
런타임 의존관계
설계와 코드에 드러나지 않는 실제 사용대상인 구체적인 의존 오브젝트와의 관계가 런타임 시에 설정되어 드러나는 것을 말한다. 그리고 이러한 의존관계를 런타임 시에 설정해 주는 제3의 존재가 애플리케이션 컨텍스트다.
DL(Dependency Lookup, 의존관계 검색)
스프링은 의존관계를 맺는 방법이 외부로부터의 주입받는 의존관계 주입 말고도 스스로 의존관계 검색을 통해 자신이 필요로 하는 의존 오브젝트를 능동적으로 찾는 방법도 존재한다. 이 방법은 의존관계를 맺을 오브젝트를 결정하거나 생성하는 것은 스프링 컨테이너에게 권한을 넘기지만, 가져올 때는 컨테이너에게 요청하는 방법을 사용한다.
DI와 DL의 차이
DI는 오브젝트와 오브젝트 사이의 의존관계를 설정하기 위해서는 컨테이너가 생성과 초기화 권한을 갖고 있어야 주입해 줄 수 있기 때문에 두 오브젝트 모두 빈으로 등록이 되어 있어야 한다. 하지만 DL은 검색을 하려는 대상 오브젝트만 빈이면 검색하는 오브젝트 자신이 빈일 필요가 없다.
외부로부터 레퍼런스를 주입받으면 모두 DI일까?
레퍼런스를 주입받는다고 모두 DI는 아니다. DI는 런타임 시에 의존관계가 결정되어 주입되어야 하는데 이미 파라미터의 타입으로 구체적인 클래스 타입이 명시되어 있다면 DI를 받는다고 할 수 없다.
DI의 장점
객체지향 설계와 프로그래밍 원칙을 따른 기술이기 때문에 구체적인 의존관계가 드러나지 않고, 인터페이스를 통해 결합도를 낮추고, 의존관계의 대상이 변경에 영향을 받지 않으며, 확장에는 자유로워 진다.
이로 인해 기능 구현의 교환이 자유롭고(예:기존에 사용하던 DB에서 다른 DB로의 교환), 기존코드의 변경 없이 새로운 런타임 의존관계를 설정하여 부가기능도 자유롭게 추가할 수 있다.
생성자를 이용한 의존관계 주입
생성자에 파라미터를 만들어 이를 통해 스프링 컨테이너가 의존할 오브젝트 레퍼런스를 넘겨주는 방식이다. 한 번에 필요한 모든 파라미터를 다 받아야 한다.
메서드를 이용한 의존관계 주입
수정자 메서드와 초기화 메서드를 이용해서도 의존관계를 주입할 수 있다. 생성자를 이용한 의존관계 주입과는 다르게 한 번에 모든 파라미터를 다 받을 필요 없이 필요에 따라 받아서 주입할 수 있다. 수정자 메서드 방식은 한 번에 하나의 파라미터만 받아야 한다는 단점이 있고, 초기화 메서드 방식은 파라미터 수의 제한이 없다. 주로 자바빈 규약을 따르는 수정자 메서드 방식을 사용한다.
'Back-End > Spring' 카테고리의 다른 글
토비의 스프링으로 면접 준비하기 (0) | 2024.04.08 |
---|---|
엔티티에서 값을 의미 있게 표현하는 방법은 무엇일까? (0) | 2024.04.05 |
[로깅] MDC를 이용한 식별된 로깅 처리 (1) | 2024.03.05 |
[JPA] N + 1 문제와 해결 방법 (1) | 2024.03.01 |
슬라이스 테스트 (0) | 2023.07.05 |