[토비의 스프링 3.1 vol.1] 1. 오브젝트와 의존관계

60jong·2022년 8월 20일
0
post-thumbnail

오브젝트

스프링은 자바를 기반으로 함으로써 자바 엔터프라이즈 기술의 혼란 속에서 잃어버렸던 객체지향 기술의 진정한 가치를 회복시키고, 객체지향 프로그래밍이 제공하는 폭넓은 혜택을 누릴 수 있도록 기본으로 돌아가자는 것이 핵심 철학.

오브젝트에 가장 큰 관심

오브젝트의 기술적인 특징과 사용 방법을 넘어서 오브젝트의 설계로 발전 (디자인 패턴, 리팩토링, 단위 테스트 등 지식 요구)

자바 빈, 아래 두가지 관례를 다라 만들어진 오브젝트

  • 디폴트 생성자 : 자바빈은 파라미터 없는 디폴트 생성자 필요 (리플렉션을 이용해 오브젝트를 만들기 때문.)
  • 프로퍼티 (수정자 메소드 setter, 접근자 메소드 getter를 통해 수정, 접근이 가능)

만들어진 코드를 검증할 때 가장 간단한 방법은 오브젝트 스스로 자신을 검증하도록 만들어주는 것. main()을 이용해!

그런데 UserDao는 문제가 많다 (동작은 제대로 하지만...)

관심사의 분리

개발자가 객체를 설계할 때 가장 염두할 상황은 미래의 변화를 어떻게 대비할 것인가?

분리... 예로 DBMS가 mysql -> oracle

그래서 관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모이게 하고, 다른 것은 서로 영향 주지 않게 분리

추상 클래스를 활용

  • 템플릿 메소드 패턴
    슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상 메소드나 오버라이팅이 가능한 protected 메소드 등으로 만든 뒤 서브클래스에서
    필요에 맞게 구현해사 사용하도록 한다. (스프링에서 애용)
  • 팩토리 메소드 패턴
    서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것

이렇게 두 패턴은 관심사가 다른 코드를 분리하고, 이를 독립적으로 확장할 수 있게 한다.

하지만 상속을 사용했다는 단점이 있다. 자바는 다중 상속이 불가능하기에 추후에 UserDao에 상속을 적용하기 어렵고, 상속 상하위 클래스의
관계는 밀접하다(슈퍼클래스에 변경이 생기면 모든 서브 클래스도 같이 수정해야한다.)

클래스의 분리

추상 클래스, 상속 대신 클래스를 따로 분리

그런데 클래스를 분리하는 경우 한 클래스가 다른 클래스에 종속되는 문제가 발생한다. 이는 확장에 문제를 준다.
따라서 두 클래스 사이에 추상적인 느슨한 연결 고리를 할 수 있는

인터페이스 도입

한 클래스에는 인터페이스를 통해 역할만을 제공하고,
실제 구현은 인터페이스를 구현한 클래스를 통하는 것이다. 그런데 결국에는 Connection connection = new DConnection(); 이렇게 특정한 구현체에
종속받기 마련이다. 이렇게 되면 확장에 문제를 주는건 여전하다.

관계설정 책임의 분리

이렇게 한 클래스가 확장이 가능하려면 구현체와 분리되어야한다. -> 인터페이스 - 클래스 사이의 관계가 아니라 "오브젝트" 사이의 관계를 설정
생성자 내부에서 오브젝트를 생성하지 말고 외부에서 생성된 오브젝트를 파라미터로 전달 받는 방식으로!!!
클래스 사이의 관계와 오브젝트 사이의 관계를 잘 구분하라.

  • 클래스 사이의 관계는 코드로 직접적인 클래스 구현체를 선언하게 된다.
  • 오브젝트 사이의 관계는 ... 한 오브젝트에서 필요로 하는 인터페이스 타입으로 구현된 오브젝트라면 관계를 설정, "사용"할 수 있게 된다.

정리하자면, 클래스의 생성자를 통해 구현체를 할당하는 것이 아니라 클래스를 사용하는 제 3의 오브젝트에서 두 클래스의 오브젝트들을 할당하는 방식
으로 책임을 바꾸는 것이다.

원칙과 패턴

OCP -- 높은 응집도와 낮은 결합도

응집도가 높다 -> 하나의 모듈, 클래스가 하나의 관심사에만 집중되어있다. 변경이 있을 때, 모듈에 변하는 부분이 크다.
결합도가 낮다 -> 하나의 오브젝터가 변경이 일어날 때에 관계를 맺고 있는 다른 오브젝트에게 변화를 요구하는 정도.

전략 패턴
디자인 패턴의 꽃, OCP 실현에도 가장 잘 들어맞는다. 전략 패턴이란, 클라이언트가 전략을 생성해 전략을 실행할 컨텍스트에 주입하는 패턴이다.
필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 필요에 따라 바꿀 수 있게 하는 디자인 패턴

스프링은 이러한 객체지향적 설계 원칙과 디자인 패턴에 나타난 장점을 자연스럽게 개발자가 활용할 수 있게 해주는 프레임워크이다.

IoC 제어의 역전

Inversion of Control

오브젝트 팩토리

클래스에서는 성격이 다른 책임이나 관심사는 분리해왔다.
팩토리
팩토리라 불리는 오브젝트는 설계도 역할을 하는데, 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려준다.

제어권의 이전을 통한 제어관계 역전

제어의 역전 : 프로그램의 제어 흐름 구조가 뒤바뀌는 것

일반적으로 프로그램에서 오브젝트는 프로그램 흐름을 결정하거나 오브젝트를 구성하는 작업에 능동적으로 참여한다. 제어의 역전은 이런 제어의 흐름의 개념을 거꾸로 뒤집는다. 다시 말해 오브젝트는 자신이 사용할 오브젝트를 스스로 선택하지 않는다. 심지어 생성하지도 않는다. 제어 권한을 갖는 특별한 오브젝트에 의해 오브젝트가 결정되고 만들어지게 된다. 이것이 우리가 공부한 내용이다.

프레임워크도 제어의 역전이 적용된 대표적인 기술이다. 프레임워크는 라이브러리와 다르다. 라이브러리를 사용하는 코드는 애플리케이션 흐름을 직접 제어한다. 하지만 프레임워크는 프레임워크에 의해 코드가 사용된다. 핵심은 제어의 역전의 유무이다.

저자는 "애플리케이션 코드는 프레임워크가 짜놓은 틀에서 수동적으로 동작해야한다."라고 설명한다.

내가 이해한 바로는
프레임워크 내에서 클래스는 내부에서 사용할 구현체를 직접 결정, 생성하는 것이 아니라 인터페이스로 선언해 역할만을 부여하고, 프레임워크로 부터 어느 구현체, 오브젝트를 사용할 것인지 결정받고, 프레임워크에 의해 오브젝트와 연결이 되어야한다는 것이다.

스프링의 IoC

스프링의 핵심은 빈 팩토리와 애플리케이션 컨텍스트이다.
스프링에서는 스프링이 제어권을 갖고 직접 만들고 관계를 부여하는 오브젝트를 빈이라고 한다. 이 빈에는 제어의 역전이 적용돼있다. 이렇게 빈들의 생성과 관계설정과 같은 제어를 담당하는 IoC오브젝트를 빈 팩토리 라고 한다. 그리고 애플리케이션 컨텍스트는 빈 팩토리를 조금 확장한 개념이다. 엄밀히 말하자면 설정정보를 담고 있는 무엇인가를 가져와 이를 활용하는 범용적인 IoC엔진이다.

스프링에서는 이러한 IoC오브젝트, 설정정보임을 명시해주는 방법은 @Configuration 애노테이션을 추가하는 것이다. 그리고 오브젝트를 만들어주는 메소드에는 @Bean을 붙인다. 그리고 이 설정정보 클래스를 애플리케이션 컨텍스트, ApplicationContext타입의 오브젝트의 파라미터로 입력함으로써 IoC 오브젝트를 생성한다.

애플리케이션 컨텍스트의 호칭

  • IoC 컨테이너

  • 스프링 컨테이너

  • 빈 팩토리 (BeanFactory를 상속했기에)

    애플리케이션 컨텍스트는 넘어온 설정정보에서 @Bean이 붙은 메소드들의 이름을 가져와 빈 목록에 등록하고, getBean()을 통해 빈을 조회하게 되면 빈 목록에서 요청한 이름이 있는지 찾고, 있다면 메소드를 호출해서 오브젝트를 생성한 다음, 클라이언트에게 돌려준다.

싱글톤 레지스트리와 오브젝트 스코프

  • 오브젝트의 동일성과 동등성
    • 동일성, identity, 두 개의 오브젝트가 완전히 같다. ==
    • 동등성, equality, 두 개의 오브젝트가 동일한 정보를 담고 있다. equals()

기존의 오브젝트 팩토리를 통해서는 호출시에 객체를 생성하기 때문에 호출할 때마다 다른 오브젝트가 생성된다.
하지만 애플리케이션 컨텍스트에서 getBean()을 통해 빈을 호출하면 매번 같은 오브젝트가 호출된다.
이는 호출 시에 매번 new에 의해 새로운 오브젝트가 만들어지는 것은 아님을 시사한다.

싱글톤 레지스트리로서의 애플리케이션 컨텍스트

애플리케이션 컨텍스트는 IoC컨테이너이자, 싱글톤 레지스트리이다. 빈 오브젝트를 모두 싱글톤으로 만든다는 것이다.

이렇게 빈 오브젝트를 싱글톤으로 만드는 이유는, 스프링이 적용되는 대상이 자바 엔터프라이즈 기술을 사용하는 서버환경이기 때문이다. 매 요청마다 새로운 오브젝트를 만들게 될 경우, 많은 요청이 들어왔을 때 서버에 많은 부담을 주게 된다. 따라서 대부분의 빈 오브젝트를 싱글톤으로 만듦으로써 이에 대응하는 것이다.

싱글톤 패턴의 한계

자바에서 싱글톤을 구현하는 방법

  • 클래스 밖에서는 오브젝트를 생성하지 못하게 생성자를 private으로 만든다.
  • 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 타입의 스태틱 필드를 정의한다.
  • 스태틱 팩토리 메소드인 getInstance()를 만들고 이 메소드가 최초로 호출되는 시점에서 한 번만 오브젝트가 만들어지게 한다. 생성된 오브젝트는 스태틱 필드에 저장된다. 또는 스태틱 필드의 초기값으로 오브젝트를 미리 만들어둘 수 있다.
  • 한번 오브젝트가 만들어지고 난 후에는 getInstance() 메소드를 통해 이미 만들어져 스태틱 필드에 저장해둔 오브젝트를 넘겨준다.

싱글톤 패턴의 한계는

  • private 생성자를 갖고 있기 때문에 상속할 수 없다.
  • 싱글톤은 테스트하기 힘들다.
  • 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
    • 서버환경에서는 여러 개의 JVM에 분산돼 설치가 되는 경우에는 각각의 JVM에서 독립적인 오브젝트가 생성되기에.
  • 싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.
    • 오브젝트가 전역 상태가 된다면, critical section이게 된다.

싱글톤 레지스트리

스프링은 싱글톤 레지스트리를 통해 직접 싱글톤 형태의 오브젝트를 만들고 관리한다. 기존의 싱글톤 패턴과는 다르게 일반적인 자바 클래스를 통해 싱글톤을 활용하게 해준다. 그래서 기존의 싱글톤 패턴의 한계를 극복하고 스프링의 객체지향 설계의 원칙에 잘 부합하게 된다.

그리고 싱글톤 오브젝트는 자바의 멀티쓰레드 환경에서 사용되기 때문에 기본적으로 무상태 (stateless)방식으로 만들어야한다.

의존관계 주입(DI, Dependenct Injection)

제어의 역전(IoC)과 의존관계 주입

스프링은 IoC기능을 기반으로 만들어진 컨테이너이자 프레임워크이다. 폭 넓은 IoC개념에서 의존관계 주입이라는 개념을 강조하고자 스프링은 DI컨테이너라고도 불린다. IoC는 주로 DI에 초점이 맞춰져 있다.

의존관계

클래스 A, B가 있다. A가 B를 사용할 경우, "A는 B에 의존하고 있다"라고 표현하고, "A와 B는 의존관계에 있다"라고 표현한다. 하지만 B는 A에 의존하지 않는다. 이처럼 의존관계는 방향성이 중요하다.

인터페이스를 통해 의존관계를 설정해두면, 구현체에 변화가 생겨도 의존관계에 있어 따로 수정할 것이 없으니 결합도가 낮아진다.
이는 UML 설계 모델의 관점.

런타임 환경에서는 오브젝트 사이에서 만들어지는 오브젝트 의존관계(런타임 의존관계)가 존재하는데, 인터페이스의 구현체 오브젝트는 의존 오브젝트라고 부르고

DI는 이러한 의존 오브젝트와 이를 사용할 주체 오브젝트를 런타임시에 컨테이너 혹은 팩토리가 연결해주는 작업이다. 이 작업의 핵심은 설계 시점에서 알지 못했던 두 오브젝트 관계를 런타임 시에 관계를 맺도록 해주는 제3의 존재가 있다는 것이다. (컨테이너 or 팩토리) 의존관계를 주입은 오브젝트의 레퍼런스를 주입해주게 되는데 주로 생성자, 메소드를 통해 주입하게 된다.

추가적으로, IoC 방법에는 DI 뿐만 아니라 의존관계를 직접 검색하는 의존관계 검색 (Dependency Lookup)도 있다.

결론적으로 추후에 변화에 대응하기 위해 클래스에서 관심사를 분리하게 된다. 클래스간에 관계가 필요하다면 느슨한 관계를 위해 인터페이스를 통해 관계를 설정해주고, 런타임 환경에서 오브젝트를 통해 관계를 맺게 한다. 그 관계 설정은 IoC를 통한 DI를 이용한다. 이렇게 되면 각 클래스들은 자신의 관심사에만 집중할 수 있게 되고, 변화에 대응할 수 있게 된다.

예시

Q. UserDao 클래스에서 DB 접근 역할을 하는 ConnectionMaker 인터페이스를 의존하는데, 기존에 
ConnectionMaker의 구현체인 YKJConnectionMaker와 런타임 의존관계를 맺고 있었다. 그런데 
DB 접근 횟수를 세고 싶다.
  • 클래스 의존관계
    UserDao ----> ConnectionMaker (interface)
  • 런타임 의존관계
    UserDao ----> YKJConnectionMaker
public class DaoFactory {
    public UserDao userDao() {
        return new UserDao(connectionMaker());
    }

    public ConnectionMaker connectionMaker() {
        return new YKJConnectionMaker();
    }
}

수정 후,

  • 런타임 의존관계
    UserDao ----> CountConnectionMaker ---->YKJConnectionMaker
public class DaoFactory {
    public UserDao userDao() {
        return new UserDao(connectionMaker());
    }

    public ConnectionMaker connectionMaker() {
        return new CountConnectionMaker(realConnectionMaker());
    }

    public ConnectionMaker realConnectionMaker() {
        return new YKJConnectionMaker();
    }
}
profile
울릉도에 별장 짓고 싶다

0개의 댓글