오상우
오상우

Categories

  • java
  • spring

1. 오브젝트와 의존관계

유저 데이터베이스 로직을 지닌 간단한 UserDao 클래스를 만들고 리팩토링하며 객체 지향 설계와 IoC/DI 에 대한 기본 개념을 잡는 장이다.

이번에는 1장을 학습하며 중간 중간 코드를 저장해두지 않아서 중간 과정 코드가 없다. 2장부터는 기록을 위해서라도 중간중간 코드를 남겨 놓아야겠다.

1. 관심사의 분리

처음 작성한 UserDao는 데이터베이스 연결 로직과 실제 데이터를 조회하거나 추가하는 로직, 데이터베이스 연결을 끊는 로직이 모두 하나의 메소드에 들어 있어 코드의 중복, 여러 역할 존재 등 문제가 많은 코드였다. 이를 리팩토링하는 첫 번째 작업으로 관심사의 분리를 통해 데이터베이스 연결 로직과 실제 데이터 관련 로직을 서로 다른 메소드로 분리했다. 이어서 이 두 로직을 아예 다른 클래스로 분리하여 더 높은 추상화 수준으로 분리했다.

2. 클래스간 의존성 문제

이렇게 서로 다른 관심사를 다른 클래스로 분리하고 나니, 한 가지 문제가 발생했다. 바로 두 클래스 간 의존성 문제였다. 처음에는 데이터베이스 관리 로직을 상위 클래스로 두고, 데이터베이스 연결 로직은 추상 메소드로 두어 자식 클래스들이 구현하도록 하는 템플릿 메소드 패턴을 이용해 구현하였다. 템플릿 메소드 패턴도 좋은 디자인 패턴이지만, 상속을 사용한 방식이라 의존성이 비교적 많이 남아있다는 단점이 있었다.

예를 들어, 부모 클래스의 메소드가 바뀌면 자식 클래스를 사용하는 코드들에서 모두 동작이 바뀌어 예상치 못했던 문제가 발생하는 등의 문제였다.

따라서 토비는 상속이 아닌 인터페이스를 활용해 의존성 문제를 해결한다.

즉, 사용하는 클래스(데이터베이스 관리 로직)에서 프라이빗 변수로 사용되는 클래스의 참조값을 가지고, 데이터베이스 연결 로직을 해당 클래스에 위임하는 방식으로 구현하는 것이다. 이때 프라이빗 변수의 타입을 인터페이스로 구현하여, 해당 인터페이스를 구현한 클래스라면 어떤 것이든 사용 할 수 있게 하였다. 이를 흔히 전략 패턴이라고 부른다.

3. 개방 폐쇄 원칙 / Open Closed Principle

이렇게 인터페이스를 사용해 외부 오브젝트와의 결합도를 낮추고, 오브젝트 내부 응집도를 높이면 의존 객체의 확장에는 열려 있고, 의존 객체의 변화에 따른 내부 변화에는 닫혀 있는 설계가 된다.

4. 제어의 역전 / Inversion Of Control

또한 오브젝트를 생성하고 오브젝트 간 의존 관계를 결정하는 역할을 따로 빼 팩토리 클래스를 만들었다. 이렇게 각 오브젝트들은 자신이 언제 사용될지, 관계는 어떻게 맻어질 지 알 수 없게 하는 설계를 제어의 역전, IoC라고 부른다.

5. 싱글톤 패턴

싱글턴 패턴은 Gof의 디자인 패턴 책에도 등장하는 오래된 디자인 패턴 중 하나다. 초당 수십,수백개의 요청을 받아 객체들을 이용해 서비스를 제공하는 엔터프라이즈 서버 어플리케이션에서 싱글턴은 꼭 필요한 패턴이다. 하지만 직접 구현 했을때, 클래스 코드가 지저분해지고, 생성자가 프라이빗이 되는 등 단점도 많은 패턴이다. 스프링에서는 이런 싱글턴을 컨테이너와 빈의 형태로 제공해줌으로서 단점을 줄이고 장점을 극대화한다.

6. 의존성 주입 / Dipendency Injection

설계 시점에는 클래스와 인터페이스 간 느슨한 의존성만 만들어두고, 런타임시 구체적인 의존 구현 객체를 외부(DI 컨테이너)로부터 주입받는 패턴을 DI라고 부른다.

스프링은 xml / 자바 애노테이션으로 DI를 손쉽게 사용하게 도와준다.

public class UserDaoTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new GenericXmlApplicationContext("applicationContext.xml");
        UserDao userDao = applicationContext.getBean("userDao", UserDao.class);

        System.out.println(userDao);
    }
}

public class UserDao {
    private DataSource connectionMaker;

    public UserDao() {
    }

    public void add(User user) throws SQLException, ClassNotFoundException {
        Connection c = connectionMaker.getConnection();

        PreparedStatement ps = c.prepareStatement("INSERT INTO user (id, name, password) VALUES (?, ?, ?)");
        ps.setString(1, user.getId());
        ps.setString(2, user.getName());
        ps.setString(3, user.getPassword());

        ps.executeUpdate();

        ps.close();
        c.close();
    }

    public User get(String id) throws ClassNotFoundException, SQLException {
        Connection c = connectionMaker.getConnection();

        PreparedStatement ps = c.prepareStatement("SELECT * FROM user WHERE id = ?");
        ps.setString(1, id);

        ResultSet rs = ps.executeQuery();
        rs.next();
        User user = new User();
        user.setId(rs.getString("id"));
        user.setName(rs.getString("name"));
        user.setPassword(rs.getString("password"));

        rs.close();
        ps.close();
        c.close();

        return user;
    }

    public void setConnectionMaker(DataSource connectionMaker) {
        this.connectionMaker = connectionMaker;
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="connectionMaker" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost/springbook" />
        <property name="username" value="root" />
        <property name="password" value="password" />
    </bean>
    <bean id="userDao" class="springbook.user.domain.UserDao">
        <property name="connectionMaker" ref="connectionMaker" />
    </bean>

</beans>

결론

스프링은 ‘어떻게 객체(부품)가 생성되고, 어떻게 서로 관계를 맻고 사용되는지’ 에 관심을 갖는 프레임워크다. 다만 실제로 그 관계를 설계하고, 객체를 만드는 것은 오롯이 개발자의 몫이다. 좋은 객체지향 설계와 깔끔하고 유연한 코드는 개발자가 끊임없는 공부를 통해 만들어가야 한다.