본문 바로가기

Reading Record/토비의 스프링 3.0

[토비의 스프링] [1장] 오브젝트와 의존관계 1.4 ~ 1.7

 

[1.4~7] 용어정리 · Issue #169 · Java-Bom/ReadingRecord

추상 팩토리 패턴 (13ea7ab) 제어의 역전 (d248362) 빈 팩토리, 애플리케이션 컨텍스트, 컨테이너, IoC 컨테이너, 스프링의 빈 (8fd75ec} 오브젝트 동일성과 동등성, 싱글톤 레지스트리 (dca60a6) 싱글톤 패

github.com

 

추상팩토리패턴

연관된 객체를 생성하는 팩토리를 추상화함으로써 클라이언트가 어떤 객체가 생성되는지 알 필요가 없게 할 수 있는 패턴.

 

public interface CardFactory {
    Card createCard();
    CardApp createCardApp();
}



public class SamsungCardFactory implements CardFactory{
    @Override
    public Card createCard() {
        /**
         * 삼성카드만의 생성로직
         */
        return new SamsungCard();
    }

    @Override
    public CardApp createCardApp() {
        /**
         * 삼성카드 앱만의 생성로직
         */
        return new SamsungCardApp();
    }
}



/**
 * 클라이언트 코드 내부 어디에도 구체클래스는 드러나지 않는다
 */
public class CardClient {
    private final CardFactory cardFactory;

    public CardClient(final CardFactory cardFactory) {
        this.cardFactory = cardFactory;
    }

    public void useCard() {
        Card card = cardFactory.createCard();
        card.pay();
    }

    public void userCardApp() {
        CardApp cardApp = cardFactory.createCardApp();
        cardApp.use();
    }
}

 

 

 

제어의 역전

생성에 대한 책임을 다른 객체(IoC 컨테이너)에게 위임하는 것.

 

클라이언트는 IoC 컨테이너를 통해 객체를 얻는다. UserService는 자신이 사용하는 인터페이스의 구현체에 대해 알 필요가 없으며 변경되어도 코드의 변경이 있지 않다. 높은 응집도와 OCP를 만족한다고 할 수 있다.

 

public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}
public class IoCContainer {

    /**
     * IoC: 제어의 역전
     * UserService의 UserRepository 구현체에 대한 생성의 책임을 IoCContainer에 위임한다.
     *
     * @return
     */
    public UserService javabomUserService() {
        return new UserService(new JavabomUserRepository());
    }
}

 

 

빈팩토리, 애플리케이션 컨텍스트, 컨테이너, IoC 컨테이너

모두 빈의 생명주기를 관리하는 애플리케이션 컨텍스트를 의미하는 말이다.

 

애플리케이션 컨텍스트의 장점

 

1) 클라이언트는 구체적인 팩토리 클래스를 알지 못해도 된다. 아래처럼 UserRepository 라는 인터페이스로 구현체를 받을 수 있다.

UserRepository userRepository = context.getBean("defaultUserRepository", UserRepository.class);

 

2) 종합 IoC 서비스를 제공해준다

  • 싱글톤, 프로토타입, 세션, 등 다양한 스코프의 빈을 제공

 

3) 빈을 검색하는 다양한 방법제공

// 이름으로 검색
UserRepository userRepository = context.getBean("defaultUserRepository", UserRepository.class);
UserRepository defaultUserRepository = context.getBean(DefaultUserRepository.class); // 타입으로
  • @Service, @Component, @Repository, @Controller 등 애너테이션으로도 빈을 검색한다.

 

 

싱글톤 패턴

 

싱글톤 패턴은 항상 동일성을 만족하는 인스턴스를 반환하는 패턴이다.

 

싱글톤 패턴의 문제는 다음과 같다.

  • 지저분한 코드
  • JVM 환경에 따라 실제로는 싱글톤이 아닐 수도 있음
  • 상속불가, static 필드 -> 객체지향 개념을 적용할 수 없음
  • Mock 인스턴스 생성 불가. 테스트 불가.

 

싱글톤 패턴을 직접 구현한 UserDao

public class UserDao {
    private static final Object LOCK = new Object();
    private static UserDao INSTANCE;
    private final Connection connection;

    public UserDao(Connection connection) {
        this.connection = connection;
    }

    /**
     * UserDao 객체의 책임은 User모델을 다루는 로직들로만 응집되어있어야하는데
     * 책임과 전혀 무관한 싱글톤만을 위한 코드가 들아간다.
     */
    public static UserDao getInstance(Connection connection) {
        synchronized (LOCK) {
            if (Objects.isNull(INSTANCE)) {
                INSTANCE = new UserDao(connection);
            }
        }

        return INSTANCE;
    }

    public User getUser() {
        // connection 에서 user얻어옴
        Object result = connection.execute("select * from user");
        return (User) result;
    }
}

 

 

오브젝트의 동등성과 동일성, 싱글톤 레지스트리

 

동등성은 equalsTo로 비교한 결과(실제로 같은 오브젝트가 아닐 수 있음)

동일성은 == 으로 비교한 결과(실제로 같은 오브젝트)

 

    @DisplayName("IoC컨테이너는 싱글톤 레지스트리로서의 역할을 한다")
    @Test
    void singletonRegistry() {
        ApplicationContext context = new AnnotationConfigApplicationContext(ChapterOneConfiguration.class);

        UserRepository user1 = context.getBean("defaultUserRepository", UserRepository.class);
        UserRepository user2 = context.getBean("defaultUserRepository", UserRepository.class);
        // 빈의 스코프: 프로토타입
        UserRepository user3 = context.getBean("prototypeUserRepository", UserRepository.class);

        /*
          컨테이너에 의해 생성되는 오브젝트는 동일성을 만족한다.
          즉, 컨테이너는 빈의 생명주기를 관리함과 동시에 싱글톤 오브젝트를 반환하는 싱글톤 레지스트리로서의 역할을 한다.
          이때 빈의 스코프는 싱글톤
         */
        assertThat(user1).isEqualTo(user2); // 동등성 (equlasTo), 동일한 정보를 담고있다.
        assertThat(user1 == user2).isTrue(); // 동일성, 완전히 동일한 오브젝트이다
        assertThat(user1 != user3).isTrue(); // 동일성을 만족하지 않는다.
    }

 

 

빈의 스코프

  • 싱글톤: 계속 같은 인스턴스를 반환 기본 설정
  • 프로토타입: 얻을때마다 다른 인스턴스 반환
  • 요청스코프: Http 요청시마다 새로운 인스턴스 반환
  • 세션스코프: 세션이 유지될 때는 같은 인스턴스 반환. 새로운 세션의 요청에는 새로운 인스턴스 반환
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public UserRepository prototypeUserRepository() {
        return new DefaultUserRepository();
    }

    // Http 요청 올때마다 새로 만드는 Request Scope
    @Bean
    @RequestScope
    public UserRepository requestUserRepository() {
        return new DefaultUserRepository();
    }

    @Bean
    @SessionScope
    public UserRepository sessionUserRepository() {
        return new DefaultUserRepository();
    }

 

 

 

DI의 세가지 조건

 

1) 인터페이스에 의존함으로써 느슨한 결합을 가져야한다.

/*
이 코드에서 DefaultUserRepository와의 의존관계는 드러나지 않는다
*/
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void register(User user) {

    }
}

 

2) 런타임 시점의 의존관계는 컨테이너나 팩토리같은 제 3의 존재가 결정한다

3) 사용할 오브젝트에 대한 레퍼런스는 외부에서 주입(생성자, Setter,,)된다.

/*
UserService 는 이 설정 파일을 읽을 때(런타임)에 DefaultUserRepository 와 런타임 의존관계를 가진다.
여기서 DefaultUserRepository를 의존 오브젝트라고한다.(실제 사용대상의 오브젝트)
 */
@Bean
public UserService userService() {
    return new UserService(defaultUserRepository());
}

 

 

 

스프링이 제공하는 DI와 DL의 차이

 

[1.4~7] IOC 와 DI, DL · Issue #173 · Java-Bom/ReadingRecord

Ioc di dl 의 차이를 정리하면 좋을거 같아

github.com

 

DI

IoC 컨테이너에 의해 구현체를 주입받는다. 위의 DI의 세가지 조건을 만족한다

         @Bean
    public UserService userService() {
        return new UserService(defaultUserRepository());
    }

 

 

DL

스프링이 제공하는 DL 기능은 getBean을 통해 빈을 검색하는 기능이다. 빈의 이름, 타입등으로 검색할 수 있다.

생성자나 Setter를 사용한 의존 주입을 하지 않을 때, 또는 DI 받으려는 객체가 빈이 아닐 때 활용할 수 있다. (DI를 받기 위해서는 자기 자신도 빈이어야 하기 때문)

UserRepository userRepository = context.getBean("defaultUserRepository", UserRepository.class);
UserRepository defaultUserRepository = context.getBean(DefaultUserRepository.class); // 타입으로

 

 

DI 장점 - 관심사의 분리 측면

DI를 이용하면 기존의 구현체에 기능추가 등 변경이 요구되어도 코드의 변경 없이 해결할 수 있다.

https://github.com/Java-Bom/ReadingRecord/issues/174#issuecomment-723386158

 

[1.4~7] DI 장점 · Issue #174 · Java-Bom/ReadingRecord

125P 에 DI 장점, 관심사의 분리에 대해서 나오는데 이거 엄청 중요한거 같아. 예제랑 자세한 설명추가해줘

github.com