본문 바로가기
카테고리 없음

1장 오브젝트와 의존관계 (3)

by DanteMustDie 2023. 11. 17.
728x90

다음의 내용은 아래의 내용을 읽은 후 책에서 언급되는 구문을 기반으로 생각 정리한 글 입니다.

 

1.4 제어의 역전 (Inversion of Control)

1.4.1 오브젝트 팩토리

1.4.2 오브젝트 팩토리의 활용

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

 

아래는 간단한 예시 코드입니다.

1
2
3
4
// Connection 생성을 위한 인터페이스
public interface ConnectionMaker {
    Connection makeConnection() throws SQLException;
}
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
 
// 실제 Connection을 생성하는 클래스
public class DConnectionMaker implements ConnectionMaker {
    private static final String URL = "jdbc:mysql://localhost:3306/mydatabase";
    private static final String USERNAME = "username";
    private static final String PASSWORD = "password";
 
    @Override
    public Connection makeConnection() throws SQLException {
        // JDBC를 사용하여 Connection 생성
        return DriverManager.getConnection(URL, USERNAME, PASSWORD);
    }
}
 
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
 
// UserDao 클래스 - ConnectionMaker를 의존성으로 가짐
public class UserDao {
    private final ConnectionMaker connectionMaker;
 
    public UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }
 
    // 사용자 추가 메서드
    public void addUser(User user) throws SQLException {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
 
        try {
            connection = connectionMaker.makeConnection();
            String sql = "INSERT INTO users (id, name, email) VALUES (?, ?, ?)";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, user.getId());
            preparedStatement.setString(2, user.getName());
            preparedStatement.setString(3, user.getEmail());
            preparedStatement.executeUpdate();
        } finally {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
            if (connection != null) {
                connection.close();
            }
        }
    }
 
    // 사용자 조회 메서드 등 다른 메서드들...
}
 

cs
1
2
3
4
5
6
7
public class DaoFactory{
    public UserDao userDao() {
        ConnctionMaker connectionMaker = new DConnectionMaker();
        UserDao userDao = new UserDao(connctionMaker);
        return userDao;
    }
}
cs
1
2
3
4
5
6
7
public class UserDaoTest {
    public static void main(String[] args) throws SQLException {
        // DaoFactory를 통해 UserDao 객체 획득
        UserDao dao = new DaoFactory().userDao();
        // 여러 다른 테스트 코드들...
    }
}
cs

개선된 DaoTest

1
2
3
4
5
6
7
8
9
10
11
12
13
// DConnectionMaker를 이용하여 UserDao 객체 생성
        ConnectionMaker connectionMaker = new DConnectionMaker();
        UserDao userDao = new UserDao(connectionMaker);
 
        // 테스트를 위한 사용자 객체 생성
        User user = new User();
        user.setId("1");
        user.setName("Alice");
        user.setEmail("alice@example.com");
 
        // 사용자 추가 메서드 호출
        userDao.addUser(user);
        System.out.println("사용자 추가 완료");
cs

Factory가 추가되기 전 DaoTest

 

제어의 역전 (Inversion of Control)은 90년대 프로그래밍 디자인 패턴 책에도 수록되어 있을 정도로 오래된 개념이다.

위의 예제 코드는 원래 factory가 없는데, 그런 상황에서는 보통 테스트 코드에서 바로 DAO를 직접 요청하여 사용한다.

이렇게 되면 DAO와 TEST간의 결합도가 상승한다.

 

단순히 TEST와 DAO의 결합도가 상승해서 문제인 것이 아니라 test만을 수행해야하는 코드가 connectionMaker 클래스까지를 사용할지 결정하는 부분까지 포함되어있다. (DConnection/NConnection 두가지 종류가 예시로 나온다.)

여지껏 해온 객체의 성격에 따른 책임 그리고 관심사 분리를 해왔는데, 개선되기 전 daoTest는 이 관점에서 문제가 있으므로 그 위의 개선된 DaoTest처럼 Factory를 이용해 보다 테스트에만 집중 할 수 있도록 또 한번 분리를 하였다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DaoFactory{
    public UserDao userDao() {
        return new UserDao(new DConnectionMaker());
    }
 
    public accountDao accountDao() {
        return new AccountDao(new DConnectionMaker());
    }
 
    public MessageDao messageDao() {
        return new messageDao(new DConnectionMaker());
    }
}
 
cs

 

만약 Factory에서 다른 AccountDao, MessageDao등을 추가 해서 UserDao를 호출한다면ConnectionMaker 클래스를 사용하는 메소드 코드가 반복되서 사용되는 문제가 발생한다. 이도 역시 문제이므로 분리를 하는 것이 가장 좋은 방법이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DaoFactory{
    public UserDao userDao() {
        return new UserDao(connectionMaker());
    }
 
    public accountDao accountDao() {
        return new AccountDao(connectionMaker());
    }
 
    public MessageDao messageDao() {
        return new messageDao(connectionMaker());
    }
 
    public ConnectionMaker connectionMaker(){
        return new DConnectionMaker();    
    }
}
 
cs

 

 

일반적인 코드에선 main에서 속해있는 A라는 객체를 생성/메서드 사용, A에선 B라는 객체를 생성/메서드 사용 이라는 흐름이 개발자의 의도에 따라 반복되어 그 객체를 사용하는 자가 제어하는 구조다. 이에 반해 제어의 역전이란 개념은 프로그램의 흐름을 살펴보았을때 객체 스스로가 자신이 사용할 객체를 선택하지도, 생성하지도 않는다.

 

프레임워크도 제어의 역전 개념이 적용된 대표적인 기술, 예시이다. 프레임워크를 이해하려면 라이브러리와의 차이점을 알아야 하는데 라이브러리는 애플리케이션의 흐름을 필요에 따라 직접 제어하는 성질을 지녔고(개발하는 개발자가 주도적으로 사용 가능) 프레임워크는 반대로 애플리케이션 코드가 프레임워크에 의존성이 부여되어 전반적인 흐름이 프레임워크에 의해 사용된다. (개발자가 수동적으로 사용 가능, 프레임워크의 템플릿에 코드를 퍼즐조각 끼워넣듯 생각하면 된다.)

 

제어의 역전에서는 컴포넌트의 생성, 사용, 생명주기관리 등을 관리해주는 존재가 필요하다. 이를 예시코드에선 DaoFactory를 썼지만, 실제 대규모 서비스에서는 스프링 프레임워크의 기능을 사용해 도움받는 것이 훨씬 나을 것이다.

반응형