1.2에서는 상속을 이용하여 다른 연결을 가지는 UserDao를 만들었었다.
상속이 아닌 다른 방법으로도 관심사를 분리할 수 있을 것이라고 생각하기에, 아예 연결을 수행하는 독립적인 클래스를 만들어서 분리해보도록 하자.
public class SeungkyuConnection {
public Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/tobi",
"root",
"seungkyu");
}
}
이렇게 연결만을 수행하는 클래스를 만들고, 해당 클래스를 통해 Dao에서 연결을 생성하도록 만들었다.
public class UserDao {
private SeungkyuConnection seungkyuConnection;
public UserDao(){
seungkyuConnection = new SeungkyuConnection();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection connection = seungkyuConnection.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(
"insert into users(id, name, password) values(?,?,?)"
);
preparedStatement.setString(1, user.getId());
preparedStatement.setString(2, user.getName());
preparedStatement.setString(3, user.getPassword());
preparedStatement.executeUpdate();
preparedStatement.close();
connection.close();
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection connection = seungkyuConnection.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(
"select * from users where id = ?"
);
preparedStatement.setString(1, id);
var resultSet = preparedStatement.executeQuery();
resultSet.next();
var user = new User();
user.setId(resultSet.getString("id"));
user.setName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
resultSet.close();
preparedStatement.close();
connection.close();
return user;
}
}
이런 방법에도 2가지 문제점이 있다고 한다.
첫째는 이러한 리펙토링도 add와 get등 기존의 연결을 사용하고 있던 코드들을 찾아서 모두 새로운 방식의 연결로 바꿔줘야 한다는 일이다.
현재는 2가지 부분만 변경을 하면 되지만, 프로젝트가 커지고 파일이 분리될수록 해당 코드들을 모두 찾아서 바꾸어주기는 어려울 것이다.
둘째는 UserDao에서 데이터베이스의 연결에 대해 알고 있어야 한다는 점이다.
연결에 대한 정보가 SeungkyuConnection에 존재하기 때문에, 현재 UserDao는 해당 정보들에게 종속되어 버린다.
이러한 방식을 해결하기 위해, 두 클래스를 느슨하게 연결하도록 만들어주는 인터페이스를 사용한다.
인터페이스를 사용하면 해당 메서드의 정보만 제공하고, 구현에 관한 내용은 신경 쓸 필요가 없기 때문에 연결을 느슨하게 만들어준다.
Connection을 인터페이스로 만든다면 UserDao는 자신이 사용할 클래스에 대해 깊게 알 필요가 없어진다는 것이다.
이렇게 ConnectionHelper 인터페이스를 구현하고, 해당 객체를 UserDao에서 사용하는 방법으로 해결 할 수 있다.
SeungkyuConnection에서 해당 인터페이스를 구현하고, 데이터베이스의 연결에 대한 정보를 가지고 있다.
public class SeungkyuConnection implements ConnectionHelper {
public Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/tobi",
"root",
"seungkyu");
}
}
이러면 UserDao에서는 ConnectionHelper의 makeConnection 메서드를 사용하고 있기 때문에, 사용하는 클래스를 변경하더라도 add(), get()의 메서드에서 코드를 수정해줄 필요가 없다.
이렇게 만들어도, UserDao의 생성자에서 사용할 객체의 클래스 이름을 사용해 UserDao를 생성해야 한다는 문제가 있다.
private ConnectionHelper connectionHelper;
public UserDao(){
connectionHelper = new SeungkyuConnection(); //SeungkyuConnection으로 생성해줘야 함
}
이러면 데이터베이스의 연결 정보를 변경하고 싶을 때마다, UserDao의 코드를 수정해줘야 한다.
이렇게 현재 UserDao에서 직접적으로 SeungkyuConnection을 사용하기 때문에 발생한 문제이다.
UserDao에서는 인터페이스만을 사용하여 해당 클래스를 구현해야 한다.
런타임 중에 오브젝트 관계를 만들어주기 위해
private ConnectionHelper connectionHelper;
public UserDao(ConnectionHelper connectionHelper) {
this.connectionHelper = connectionHelper;
}
이렇게 생성자에서 ConnectionHelper를 구현한 객체를 받고, 해당 객체를 사용해 UserDao를 동작하도록 구현하여 UserDao와 SeungkyuConnection관의 의존관계를 제거한다.
사용할 때는
UserDao userDao = new UserDao(new SeungkyuConnection());
이렇게 연결정보를 담고있는 ConnectionHelper를 넘겨주어야 한다.
이러면 UserDao는 이제 연결과 데이터베이스의 정보에 신경쓰지 않고, ConnectionHelper를 사용해 데이터베이스를 조작할 수 있다.
개방 폐쇄 원칙
지금까지는 Dao를 사용해 다양한 방법으로 리펙토링을 해보고 장단점을 비교했다.
이것의 결과로 개방 폐쇄 원칙(OCP)에 대해 설명해보자.
현재 UserDao는 데이터베이스를 연결하는 방법으로는 확장이 가능하다.
동시에 UserDao는 핵심 로직들은 영향을 받지 않고 유지할 수 있다.
이런 것을 개방에는 열려있고, 변경에는 닫혀있는 개방 폐쇄 원칙이라고 한다.
높은 응집도와 낮은 결합도
응집도가 높다고 하는 것은 하나의 모듈과 클래스가 하나의 관심사만 집중하고 있다는 뜻이다.
현재 UserDao에서는 데이터베이스를 통해 사용자의 정보를 관리하는 것에 집중하고 있다.
여기에 데이터베이스를 연결하는 코드가 추가된다면, 사용자의 정보라는 관심사에 어긋나게 된다.
유지보수를 위해 하나의 클래스는 관심사를 가지는 기능들로 높은 응집도를 유지하도록 해야한다.
결합도는 하나의 오브젝트가 변경이 일어날 때, 관계를 맺고 있는 다른 오브젝트에게 변화를 요구하는 정도이다.
만약 인터페이스를 사용하지 않았다면, SeungkyuConnection을 변경할 때 UserDao도 계속 같이 변경해줘야 했을 것이다.
이것이 SeungkyuConnection과 UserDao간의 결합이 생기기 때문이었으며, 이를 인터페이스를 통한 의존으로 해결해주었다.
이렇게 응집도를 높이고, 결합도를 낮추는 방법으로 유지보수를 더 수월하게 할 수 있도록 해야한다.