토비의 스프링 정리 프로젝트 #1.4 제어의 역전 (팩토리 메소드, 제어의 역전)

Jake Seo·2021년 7월 6일
0

토비의 스프링

목록 보기
5/29

제어의 역전

IoC라는 약자로 많이 사용되는 제어의 역전(Inversion Of Control)이라는 용어가 있다. 스프링을 통해 많이 알려진 용어지만, 사실 제어의 역전이라는 개념은 상당히 오래 존재했다. IoC가 무엇인지 살펴보기 위해 UserDao 코드를 좀 더 개선해보자.

오브젝트 팩토리

테스트 책임의 분리

원래 UserDaoTest는 단순히 테스트를 위한 클래스였다. 그런데 어느샌가부터 어떤 구현 클래스를 사용하는지에 대한 책임이 있는 클라이언트가 되어버렸다. UserDaoTest는 온전히 테스트만 담당할 수 있도록 문제를 해결해보자.

팩토리의 등장

이 클래스의 역할은 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려줄 것이다. 이런 역할을 하는 클래스를 흔히 팩토리(factory) 라고 부른다.

추상 팩토리 패턴이나 팩토리 메소드 패턴과는 다르니 혼동하지 말자. 단순히 오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 깔끔히 분리하기 위함이다.

팩토리의 역할을 맡을 클래스 이름은 DaoFactory라고 할 것이다.

public class DaoFactory {
    public UserDao userDao() {
        return new UserDao(new DSimpleConnectionMaker());
    }
}

DaoFactory 팩토리 클래스에 userDao()라는 메소드를 만들어 UserDao 객체를 리턴하도록 해주었다. 그렇다면, 이제 UserDaoTest에 있는 내용도 바꾸어주자.

public class UserDaoTest {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        UserDao dao = new DaoFactory().userDao();

        User user = new User();
        user.setId("1");
        user.setName("제이크");
        user.setPassword("jakejake");

        dao.add(user);

        System.out.println(user.getId() + " register succeeded");

        User user2 = dao.get(user.getId());
        System.out.println(user2.getName());
        System.out.println(user2.getPassword());

        System.out.println(user2.getId() + " query succeeded");
    }
}

이제 UserDaoTestUserDao가 어떻게 만들어지는지에 대한 어떠한 책임도 없다. 그저 UserDao에 있는 메소드들을 테스트 하는 책임만을 부여받았다.

리팩토링을 했다면, 코드는 반드시 리팩토링이 끝난 시점에는 테스트해주자. 물론 여전히 잘 동작할테지만 동작하지 않는다면 어느 시점부터 동작하지 않았는지 알아내는 것은 매우 중요하다.

설계도로서의 팩토리

현재까지의 책임과 관계 분석을 해보자.

  • UserDao: 데이터 로직에 대한 책임
  • ConnectionMaker: DB 연결 기술에 대한 책임
  • DaoFactory: 오브젝트를 구성하고 관계를 정의하는 책임
  • UserDaoTest: 동작을 테스트하는 책임

여기서 UserDao, ConnectionMaker는 실질적인 로직을 담당하는 컴포넌트라면, DaoFactory는 컴포넌트의 구조와 관계를 정의한 설계도 같은 역할을 한다고 볼 수 있다.

DaoFactory를 분리했을 때의 장점은 매우 다양하지만, 그 중 애플리케이션 컴포넌트 역할을 하는 오브젝트와 애플리케이션 구조를 결정하는 오브젝트를 분리했다는 것이 가장 의미가 크다.

오브젝트 팩토리의 활용

다른 DAO들이 필요한 상황이 되었다고 가정해보자. AccountDao, MessageDao 등을 만들어야 한다. DaoFactory에 해당 DAO들을 생성해주기 위해 메소드를 아래와 같이 추가했다.

public class DaoFactory {
    public UserDao userDao() {
        return new UserDao(new DSimpleConnectionMaker());
    }

    public MessageDao messageDao() {
        return new MessageDao(new DSimpleConnectionMaker());
    }

    public AccountDao accountDao() {
        return new AccountDao(new DSimpleConnectionMaker());
    }
}

DaoFactory에서 이전에 한번 만난 적 있던 중복 코드 문제를 다시 만나게 되었다. 이전에 해결했을 때처럼 메소드 추출 기법으로 다시 중복을 제거해보자.

public class DaoFactory {
    public UserDao userDao() {
        return new UserDao(getConnectionMaker());
    }

    public MessageDao messageDao() {
        return new MessageDao(getConnectionMaker());
    }

    public AccountDao accountDao() {
        return new AccountDao(getConnectionMaker());
    }

    private DSimpleConnectionMaker getConnectionMaker() {
        return new DSimpleConnectionMaker();
    }
}

깔끔하게 정리되었다. 여전히 팩토리는 설계도로서의 역할을 하고 있다.

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

제어의 역전이란?

제어의 역전을 간단히 표현하면 프로그램 제어 흐름 구조가 뒤바뀌는 것이라고 할 수 있다.

일반적인 프로그램은 main 메소드에서 로직이 시작되며, 필요한 오브젝트를 만들고 오브젝트가 주체적으로 어떤 구현체를 사용하여 어떤 행위를 할 것인지에 대해 결정한다.

이를테면 처음 만든 UserDao는 인터페이스를 이용한 느슨한 연결도 없었고 직접 DConnectionMaker라는 구체적인 구현체를 직접 사용할 것이라고 결정했었다. 그러나 현재는 DaoFactory가 지정해주는 ConnectionMaker의 구현체를 쓰게 되었다. 여기서 제어의 역전이 일어났다.

제어의 역전이 없는 상태(처음 만들었던 UserDao)에서는 모든 오브젝트가 능동적으로 자신이 사용할 클래스를 결정하고, 언제 어떻게 그 오브젝트를 만들지 스스로 관장한다. 모든 종류의 작업을 사용하는 쪽에서 제어하는 구조이다.

제어의 역전이란 이런 제어 흐름을 거꾸로 뒤집는 것이다.

제어의 역전의 특성

제어의 역전에서는 모든 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다. 당연히 생성하지도 않는다. 자신도 어떻게 만들어지고 어디서 사용되는지를 알 수 없다. 모든 제어권한을 자신이 아닌 다른 대상에게 위임하기 때문이다.

모든 오브젝트는 위임받은 제어 권한을 가진 특별한 오브젝트에 의해 결정되고 만들어진다. 이를테면 우리가 만들었던 클래스 중에는 DaoFactory가 있다.

서블릿의 제어의 역전

스프링을 통해 서블릿을 개발하면 서버에 배포할 수는 있지만 그 실행을 개발자가 직접 제어할 수 있는 방법은 없다. 서블릿 안에 main() 메소드가 있어서 실행시킬 수 있는 것도 아니다. 대신 서블릿에 대한 제어 권한을 가진 컨테이너가 적절한 시점에 서블릿 클래스의 오브젝트를 만들고 그 안의 메소드를 호출한다.

서블릿, JSP, EJB처럼 컨테이너 안에서 동작하는 구조는 간단한 방식이긴 하지만 제어의 역전 개념이 적용되었다고 볼 수 있다.

템플릿 메소드 패턴의 제어의 역전

템플릿 메소드 패턴도 일종의 제어의 역전이라고 볼 수 있다. 제어권을 상위 템플릿 메소드에 넘기고, 자신은 필요할 때 호출되어 사용되도록 한다는 제어의 역전 개념이 들어있다. 템플릿 메소드는 제어의 역전이라는 개념을 활용해 문제를 해결하는 디자인 패턴으로 볼 수 있다.

프레임워크의 제어의 역전

프레임워크와 라이브러리의 다른 점으로 제어의 역전이 있다. 라이브러리를 사용하는 애플리케이션 코드는 애플리케이션 흐름을 직접 제어한다. 반면에 프레임워크는 거꾸로 애플리케이션 코드가 프레임워크에 의해 사용된다. 보통 프레임워크 위에 개발한 클래스를 등록해두고, 프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드를 사용하도록 만드는 방식이다.

프레임워크에는 분명한 제어의 역전 개념이 적용되어 있어야 한다.

UserDao, DaoFactory의 제어의 역전

UserDao는 이제 DaoFactory가 정해주는 구현체를 이용해 지정된 행위를 수행하는 수동적인 클래스가 되었다. UserDaoTestDaoFactory가 공급해주는 DAO를 사용할 수 밖에 없다. DAOConnectionMaker의 구현체를 생성하는 책임은 모두 DaoFactory에 있다. 이것이 제어의 역전이 일어난 상황이다.

자연스럽게 관심을 분리하고 책임을 나누고 유연하게 확장 가능한 구조로 만들기 위해 DaoFactory를 도입했던 과정이 바로 IoC를 적용하는 작업이었다고 볼 수 있다.

제어의 역전(IoC)과 스프링 프레임워크

IoC는 스프링 프레임워크만의 기술이 아니다. 프레임워크가 꼭 필요한 개념도 아니다. 단순히 디자인 패턴에서도 발견할 수 있는 것처럼 상당히 폭 넓게 사용되는 프로그래밍 모델이다. IoC를 적용하여 설계가 깔끔해지고 유연성이 증가하며 확장성이 좋아진다면 IoC 스타일의 설계와 코드를 만들어서 사용하면 된다.

제어의 역전에서는 프레임워크 또는 컨테이너와 같이 애플리케이션 컴포넌트의 생성과 관계설정, 사용, 생명주기 관리 등을 관장하는 존재가 필요하다. DaoFactory는 오브젝트 수준의 가장 간단한 IoC 컨테이너 혹은 IoC 프레임워크라고 볼 수 있다.

IoC를 애플리케이션 전반에 걸쳐 본격적으로 적용하려면 스프링과 같은 IoC 프레임워크의 도움을 받는 편이 훨씬 유리하다. 스프링은 IoC를 모든 기능의 기초가 되는 기반 기술로 삼고 있으며, IoC를 극한까지 적용하고 있는 프레임워크이다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글