2022. 5. 24. 21:37ㆍ토비의 스터디
1.1장에서 작성한 UserDao는 문제점이 많았다.
1.2장에서는 UserDao를 관심사별로 분리하고, 디자인 패턴(템플릿 메소드 패턴, 팩토리 메소드 패턴)에 대해서 알아보자
1.2.1 관심사의 분리
코드에서 분리해야 하는 것을 세가지 꼽자면
1) 변하는 것과 변하지 않는 것
2) 관심사
3) 공통 코드
로 기억하고 있다.
이렇게 분리한다면 변경이 일어날 때 필요한 작업이 최소화되며, 해당 변경이 다른 곳에 문제를 일으킬 가능성이 낮아진다.
그 중 관심사를 분리하는 것은 관심이 같은 것 끼리는 하나의 객체 안으로 또는 친한 객체로 모으고, 관심이 다른 것은 가능한 한 따로 분리하여 서로 영향을 주지 않도록 분리하는 것을 말한다.
이전 UserDao의 add메소드를 다시 한번 살펴보면,
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/springbook", "spring", "book");
//DB연결을 위한 Connection 가져오기
PreparedStatement ps = c.prepareStatement(
"insert into usere(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();
}
1) DB와 연결을 위한 커넥션을 어떻게 가져올지
2) SQL을 작성하고 실행하는 것
3) 사용한 리소스를 close()로 닫아주는 것
이 세개의 관심 사항이 한 메소드 안에 담겨있다.
가장 첫번째로 "DB와 연결을 위한 커넥션을 어떻게 가져올지" 관심사는 DB에 접근하는 다른 메소드에도 중복으로 들어있게 된다.
1.2.2 커넥션 만들기의 추출
중복된 DB 연결 코드를 getConnection()이라는 이름의 독립적인 메소드로 만들어준다.
(인텔리J에서는 ctrl+alt+m으로 메소드 추출이 가능하다)
public Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/sys", "kiseo", null );
return c;
}
기존의 자리에는 Connection c = getConnection();만 남겨놓은 채로 메소드를 추출할 수 있다.
이제 모든 메소드에서 DB의 커넥션을 가져오는 코드를 위의 한 줄로 대체할 수 있다.
만약 DB 로그인 정보가 변경되었다거나, 드라이버 클래스, URL이 변경된 경우 getConnection() 메소드의 내부만 변경하면된다.
이처럼 기존의 코드를 외부 동작방식에는 변화 없이 내부 구조를 변경해 재구성 하는 것을 리팩토링이라고 한다.
내부 설계가 개선되어 코드 이해가 쉽고, 변화에 효율적으로 대응할 수 있다. 따라서 유지보수가 쉽고 생산성이 오른다!
1.2.3 DB 커넥션 만들기의 독립
지금의 UserDao를 N사와 D사가 각기 다른 종류의 DB로 사용하고 싶어하는 상황이다.
아예 UserDao의 소스코드를 공개하고, getConnection()메소드를 입맛에 맞게 수정하라고 해도 되지만,
기존 UserDao 코드를 한 단계 더 분리하면 해결할 수 있다.
UserDao에서 getConnection()을 추상 메소드로 남겨놓고, N사와 D사가 이 메소드를 구현하면 된다.
N사와 D사는 각각 UserDao 클래스를 상속해서 NUserDao, DUserDao라는 서브클래스를 만드는 것이다.
public abstract class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
String sql = "insert into user(id, name, password) values(?,?,?)";
PreparedStatement pstmt = c.prepareStatement(sql);
pstmt.setString(1, user.getId());
pstmt.setString(2, user.getName());
pstmt.setString(3, user.getPassword());
pstmt.executeUpdate();
pstmt.close();
c.close();
}
public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
} //추상 클래스
//구현한 서브 클래스
public class NUserDao extends UserDao{
@Override
public Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/sys", "kiseo", null );
return c;
}
}
이제 UserDao의 코드는 수정할 필요도 없이 DB 연결 기능을 새롭게 정의한 클래스를 만들었다.
UserDao는 이제 변경이 용이한 수준을 넘어서 손쉽게 확장된다고 할 수 있다.
이처럼 슈퍼클래스에 기본적인 로직의 흐름(커넥션 가져오기, SQL 실행, 반환 등)을 만들어놓고, 그 기능의 일부를 서브클래스에서 구현하는 방법을 템플릿 메소드 패턴이라고 한다.
그리고 UserDao의 서브클래스에서 getConnection() 메소드는 어떤 Connection 클래스의 오브젝트를 어떻게 생성할 것인지를 결정한다. 이렇게 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것을 팩토리 메소드 패턴이라고 한다.
getConnection() 메소드가 생성한 Connection 오브젝트의 구현 클래스는 각각 다를 것이다. 하지만 UserDao는 Connection 인터페이스 타입의 오브젝트라는 것만 알뿐 구체적인 타입이 무엇인지는 관심이 없다. 구현이 아닌 역할(추상)에 의존하기 때문이다.
그러나 이 방법은 상속을 사용했다는 단점이 있다. 자바는 단일 상속이기때문에 후에 다른 목적으로 UserDao에 상속을 적용하기 힘들다.
또, 상속을 통한 상하위 클래스 관계는 생각보다 응집도가 높다는 점이다. 서브클래스에서 슈퍼클래스의 기능을 직접 사용할 수 있기 때문에, 슈퍼클래스의 내부 변경이 있을 때 서브클래스를 함께 수정해야 할 수도 있다.
마지막으로 확장된 기능인 DB커넥션을 생성하는 코드를 다른 DAO 클래스에 적용할 수 없다는 점이다. 만약 UserDao 이외의 DAO 클래스가 탄생한다면, 그때는 상속을 통해 만들어진 getConnection()의 구현코드가 매 DAO 클래스마다 중복되어 나타나는 문제가 발생할 것이다.
'토비의 스터디' 카테고리의 다른 글
[토비의 스프링 3.1] 1.5 스프링의 IoC (0) | 2022.05.27 |
---|---|
[토비의 스프링 3.1] 1.4 제어의 역전(IoC) (0) | 2022.05.26 |
[토비의 스프링 3.1] 1.3 DAO의 확장 (0) | 2022.05.25 |
[토비의 스프링 3.1] 1.1 초난감 DAO - 포포 (0) | 2022.05.23 |
들어가며 (0) | 2022.05.23 |