Connection을 통한 오브젝트와 의존관계의 이해
public class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/springbook","spring","book");
PreparedStatement ps = c.prepareStatement(
"insert into users(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 {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/springbook", "spring","book");
PreparedStatement ps = c.prepareStatement(
"select * from users 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 static void main(String[] args) throws SQLException, ClassNotFoundException {
UserDao dao = new UserDao();
User user = new User();
// 이하 생략
}
}
1. 관심사의 분리 이해 ( 독립된 메소드를 만들어서 분리 )
여러 메소드에서 중복코드를 사용 할 경우 해당 코드를 메소드의 분리를 통해 중복코드를 없앨 수 있다.
public class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
// 이하 생략
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
// 이하 생략
}
public Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/springbook", "spring","book");
return c;
}
}
- 문제점
- 메소드 단위가 아닌 클래스 단위에서 공통적으로 사용되는 기능일 경우 메소드에서 발생한 중복 코드와 같은 상황이 발생한다.
다른 Dao에서도 Connecction을 사용하기 위해서는 각각의 클래스에서 중복된 코드를 작성 해줘야 하기 때문에 만약 Connection이 바뀔 경우 모든 Connection을 일일이 바꿔줘야 한다.
- 메소드 단위가 아닌 클래스 단위에서 공통적으로 사용되는 기능일 경우 메소드에서 발생한 중복 코드와 같은 상황이 발생한다.
2. 상속을 통한 확장 ( 상하위 클래스(extends)로 분리 )
상위 클래스는 abstract로 구현해야 하는 메소드를 선언만 해놓고 하위 객체에서 어떤 DB를 사용할지에 대해서는 신경쓰지 않는다.
하위 클래스에서는 오버라이딩된 abstract 메소드를 구현해 각각의 객체가 원하는 DB 설정이 가능하다.
public abstract class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
// 이하 생략
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
// 이하 생략
}
public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}
- 문제점
- 상속을 쓴다는 것에 가장 큰 문제점이다.
자바는 다중 상속이 불가하기 때문에 오직 하나의 기능만을 위해 상속한다는 것은 효율성?이 떨어진다.
- 상속을 쓴다는 것에 가장 큰 문제점이다.
3. 클래스의 분리 ( 독립적인 클래스 생성 )
상속의 문제점이 사라졌고 abstract를 사용하지 않아도 된다.
public class UserDao {
private SimpleConncectionMaker simpleConncectionMaker;
public UserDao(){
simpleConncectionMaker = new SimpleConncectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = simpleConncectionMaker.makeNewConnection();
// 이하 생략
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = simpleConncectionMaker.makeNewConnection();
// 이하 생략
}
}
public class SimpleConncectionMaker {
public Connection makeNewConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/springbook", "spring","book");
return c;
}
}
- 문제점
- 다시금 문제가 발생된 것은 NUserDao와 DUserDao가 DB커넥션 생성 기능을 변경할 방법이 없어졌다.
- 이번에는 UserDao가 simpleConnectionMaker와 밀접한 관계가 되어 DB Connection 을 가져오는 방법 1가지에 종속적이게 되었다.
4. 인터페이스의 도입 ( 추상화의 구현 )
인터페이스의 도입을 통해 두 개의 클래스가 서로 긴밀하게 연결되어 있지 않도록 중간에 추상적인 느슨한 연결고리를 만들어주는 것이다.
인터페이스는 자신을 구현한 클래스에 대한 구체적인 정보는 모두 감춰버린다.
최소한의 통로를 통해 접근하는 쪽에서는 오브젝트를 만들 때 사용할 클래스가 무엇인지 몰라도 된다.
즉 인터페이스를 통해 접근하게 하면 실제 구현 클래스를 바꿔도 신경 쓸 일이 없다.
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao(){
connectionMaker = new DConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
// 이하 생략
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
// 이하 생략
}
}
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException;
}
- 문제점
- 여전히 남아있는 문제는 생성한 Interface와 Interface를 구현한 클래스를 사용하기 위해서, 생성자를 만들때 어떤 구현 클래스를 사용 할지에 대해서 남아있다는 것이다.
ex) public UserDao(){ connectionMaker = new DConnectionMaker(); }
- 결국 또 다시 원점이다. 여전히 필요할 때마다 생성자 메소드를 직접 수정하지 않는이상 자유로운 DB 변경이 불가능하다.
5. 관계설정 책임의 분리 ( 클라이언트와 서비스의 역할에 따른 책임의 분리 )
Object의 구분을 통한 방법
- 사용되는 Object, 서비스
- 사용하는 Object, 클라이언트
클라이언트 Object가 제 3의 관심사항인 UserDao와 ConnectionMaker 구현 클래스의 관계를 결정해주는 기능을 분리해서 두기에 적절한 곳이다.
UserDao의 모든 코드는 ConnectionMaker 인터페이스 외에는 어떤 클래스와도 관계를 가져서는 안 되게 해야 한다.
이 클라이언트 Object에서 클래스와 클래스 사이의 관계가 아닌 오브젝트와 오브젝트 사이의 관계를 설정해줘야 한다.
- 클래스 사이의 관계는 코드에 다른 클래스 이름이 나타나기 때문에 만들어지는 것
- ex) UserDao는 DConnectionMaker 클래스와 관계가 있음을 코드에서 알 수 있다.
- 오브젝트 사이의 관계는 클래스의 오브젝트를 인터페이스 타입으로 받아서 사용하는 것
- ex) UserDao는 Interface의 오브젝트와 관계가 있음을 클라이언트에서 설정해준다.
- 풀어보자면 관심사의 설정을 클라이언트에게 떠넘기는 것이다.
오브젝트 사이의 관계는 코드에서는 특정 클래스를 전혀 알지 못하더라도 해당 클래스가 구현한 인터페이스를 사용했다면, 그 클래스의 오브젝트를 인터페이스 타입으로 받아서 사용할 수 있다.
이것이 바로 객체지향 프로그램의 다형성이라는 특징 덕분이다.
이로써 UserDao는 본연의 Dao의 역할에 집중할 수 있는 환경이 만들어졌다고 볼 수 있다.
이를 객체지향 설계 원칙의 관점으로 보았을 때, 개방 폐쇄 원칙을 잘 지킨 사례라고 볼 수 있다.
public class UserDao { // 서비스
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker){
this.connectionMaker = connectionMaker;
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
// 이하 생략
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
// 이하 생략
}
}
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException;
}
public class UserDaoTest { // 클라이언트
public static void main(String[] args) throws SQLException, ClassNotFoundException {
// 클라이언트에서 커넥션 인터페이스 활용해서 생성 및 생성자 주입
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao dao = new UserDao(connectionMaker);
// 이하 생략
}
}
출처 : 토비의 스프링
'Programming > Backend' 카테고리의 다른 글
Spring Request 데이터를 List 형태로 받으면 @Valid 체크가 안되는 현상 해결 방법 (1) | 2021.12.04 |
---|---|
협업을 위한 snake_case -> camelCase 변환 방법 @JsonNaming, @JsonProperty (0) | 2021.11.22 |
API 명세서 뜯어보기 - StringTokenizer Class (0) | 2021.10.05 |
Mock && Mockito : 가짜 객체 (0) | 2021.10.03 |
JUnit : 자바에서 사용하는 가장 대표적인 단위 테스트 프레임 워크 (0) | 2021.10.03 |