스프링 복습
리팩토링 전
UserDao 전체 코드
UserDao의 각 메소드는 크게 DB연결, 쿼리 요청, 결과 처리, 리소스 반환 (close()) 등의 역할을 하고 있다.
예시 코드
하나의 클래스, 메소드에서 너무 많은 역할을 맞고 있고 예를 들어 DB연결방법이 바뀌면 각 메소드를 찾아가 코드를 수정해야하는 번거로움이 있다.
메소드 분리
public UserDao {
private Connection getConnection() throws ClassNotFoundException, SQLException {
Map<String, String> env = System.getenv();
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection(env.get("DB_HOST"), env.get("DB_USER"), env.get("DB_PASSWORD"));
}
public void add(final User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
// ...
}
// ...
}
Java
복사
DB연결이 모든 메소드에서 반복되고 있으므로 먼저 메소드로 분리해보았다.
getConnection() 메소드에서 DB에 연결하고 그 객체(Connection)을 반환해준다.
그리고 DB연결이 필요한 메소드에서 getConnection() 메소드를 사용해 Connection 을 받아와 사용한다.
인터페이스 사용
위 코드에서 getConnection() 메소드는 UserDao 에서만 사용가능하다.
UserDao 외에 여러 Dao 클래스들을 만들경우 Dao 클래스 안에 각각 getConnection() 을 만드는 것은 매우 비효율적이다. 또한 메소드로 분리한 이유가 DB 연결 방법이 바뀔시 수정하는 코드를 줄이기 위함이었는데, Dao 클래스 마다 getConnection() 메소드를 작성한다면 또 다시 일일이 getConnection() 메소드를 찾아 수정해야할 것이다.
DB연결 과정을 인터페이스와 구현체로 아예 Dao 클래스 밖으로 분리해보자.
ConnectionMaker
public interface ConnectionMaker {
Connection getConnection() throws ClassNotFoundException, SQLException;
}
Java
복사
ConnectionMaker 인터페이스에서 DB연결 객체인 Connection 을 반환해주는 getConnection() 메소드를 정의한다.
AwsConnectionMaker
public class AwsConnectionMaker implements ConnectionMaker {
@Override
public Connection getConnection() throws ClassNotFoundException, SQLException {
Map<String, String> env = System.getenv();
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection(env.get("DB_HOST"), env.get("DB_USER"), env.get("DB_PASSWORD"));
}
}
Java
복사
AwsConnectionMaker : ConnectionMaker 인터페이스의 구현체
실질적으로 DB연결하는 방법을 구현하고 반환해주는 객체이다.
만약, Aws 서버가 아닌 로컬환경의 DB를 사용하고 싶다면?
AwsConnectionMaker 와 마찬가지로 ConnectionMaker 인터페이스를 구현한 LocalConnectionMaker 를 작성해주면 된다.
UserDao
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
public void add(final User user) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.getConnection();
// ...
}
}
Java
복사
UserDao 안에서 위에서 분리한 AwsConnectionMaker 나 LocalConnectionMaker 를 사용하기 위해 직접 객체를 생성한다면 분리한 의미가 없다.
AwsConnectionMaker 를 사용하기 위해 생성자를 만들고
public UserDao(AwsConnectionMaker awsConnectionMaker) {
this.awsConnectionMaker = awsConnectionMaker;
}
Java
복사
LocalConnectionMaker 를 사용하기 위해 생성자를 또 만들어야하기 때문이다.
public UserDao(LocalConnectionMaker localConnectionMaker) {
this.localConnectionMaker = localConnectionMaker;
}
Java
복사
따라서, 인터페이스로 분리하고 다형성을 활용하여 “주입” 받는 것이다.
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
Java
복사
UserDaoTest
public class UserDaoTest {
private UserDao dao = new UserDao(new AwsConnectionMaker());
@Test
// ...
}
Java
복사
(위 아래는 같은 코드이다.)
public class UserDaoTest {
ConnectionMaker awsConnectionMaker = new AwsConnectionMaker();
private UserDao dao = new UserDao(awsConnectionMaker);
@Test
// ...
}
Java
복사
내가 Aws DB말고 로컬 DB에 연결하고 싶다면 위 코드들에서 그저 AwsConnectionMaker 객체를 LocalConnectionMaker 객체로 변경해주기만하면 끝이다. 다른 파일은 수정할 필요가 없다.
public class UserDaoTest {
private UserDao dao = new UserDao(new LocalConnectionMaker());
@Test
// ...
}
Java
복사
Factory 적용
위 UserDaoTest 클래스에서 처음 작성한 UserDao 클래스처럼 여러 역할을 가지고 있다.
Test를 실행하는 가장 중요한 역할과 AwsConnectionMaker 객체를 생성해서 UserDao 객체에 주입하는 역할을 맡고 있다.
두 번째 역할인 주입을 대신 맡아줄 UserFactory 클래스를 만들어보자.
UserDaoFactory
public class UserDaoFactory {
public UserDao userDao() {
return new UserDao(connectionMaker());
}
public ConnectionMaker connectionMaker() {
return new AwsConnectionMaker();
}
}
Java
복사
UserDaoTest
public class UserDaoTest {
UserDaoFactory userDaoFactory = new UserDaoFactory();
private UserDao dao = userDaoFactory.userDao();
@Test
// ...
}
Java
복사
객체 생성을 맡아주는 UserDaoFactory 덕분에 UserDaoTest 는 Test에 집중할 수 있게 되었다.