티스토리 뷰
1장 오브젝트와 의존관계
1.1 초난감 DAO
- DAO
- DAO(Data Access Object)는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트를 말한다.
- 자바빈
- 자바빈(JavaBean)은 원래 비주얼 툴에서 조작 가능한 컴포넌트를 말한다.
- 자바의 주력 개발 플랫폼이 웹 기반의 엔터프라이즈 방식으로 바뀌면서 비주얼 컴포넌트로서 자바빈은 인기를 잃어갔지만, 자바빈의 몇 가지 코딩 관례는 JSP 빈, EJB와 같은 표준 기술과 자바빈 스타일의 오브젝트를 사용하는 오픈소스 기술을 통해 계속 이어져 왔다.
- 이제는 자바빈이라고 말하면 비주얼 컴포넌트라기보다는 다음 두가지 관례를 따라 만들어진 오브젝트를 가리킨다. 간단히 빈(Bean)이라고 부르기도 한다.
- 디폴트 생성자 : 자바빈은 파라미터가 없는 디폴트 생성자를 갖고 있어야 한다. 툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문에 필요하다.
- 프로퍼티 : 자바빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는 set으로 시작하는 수정자 메소드(setter)와 get으로 시작하는 접근자 메소드(getter)를 이용해 수정 또는 조회할 수 있다.
- 사용자 정보를 DB에 넣고 관리할 수 있는 DAO 클래스
- JDBC를 이용하는 작업의 일반적인 순서
- DB 연결을 위한 Connection을 가져온다.
- sql을 담은 Statement(또는 PreparedStatement)를 만든다.
- 만들어진 Statement를 실행한다.
- 조회의 경우 SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 오브젝트(User)에 옮겨준다.
- 작업 중에 생성된 Connection, Statement, ResultSet 같은 리소스는 작업을 마친 후 반드시 닫아준다.
- JDBC API가 만들어내는 예외를 잡아서 직접 처리하거나, 메소드에 throws를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다.
- JDBC를 이용하는 작업의 일반적인 순서
public class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mtsql://localhost/springbook", "spring", "book");
PreparedStatement ps = c.preparedStatement("insert into users(id, name, password) values(?, ?, ?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
ps.excuteUpdate();
ps.close();
c.close();
}
public User get(String id) throws ClassNotFoundException, SQLException {
class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mtsql://localhost/springbook", "spring", "book");
PreparedStatement ps = c.preparedStatement("se;ect * from users where id = ?");
ps.setString(1, id);
ResultSet rs = ps.excuteQuery();
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;
}
}
- main()을 이용한 DAO 테스트 코드
- 만들어진 코드의 기능을 검증하고자 할 때 사용할 수 있는 가장 간단한 방법
- 오브젝트 스스로 자신을 검증하도록 만들어주는 것
- 모든 클래스에는 자신을 엔트리 포인트로 설정해 직접 실행이 가능하게 해주는 static 메소드 main이 있다.
public static void main(String[] args) throws ClassNotFoundException, SQLException {
UserDao dao = new UserDao();
User user = new User();
user.setId("id01");
user.setName("이름01");
user.setPassword("password01");
dao.add(user);
System.out.println(user.getId() + "등록 성공");
User user2 = dao.get(user.getId());
System.out.println(user.getName());
System.out.println(user.getPassword());
System.out.println(user2.getId() + "조회 성공");
}
1.2 DAO의 분리
- 관심사의 분리
- 객체지향의 세계에서는 모든 것이 변한다.
- 오브젝트에 대한 설계와 이를 구현한 코드가 변한다는 뜻
- 개발자가 객체를 설계할 때 가장 염두에 둬야 할 사항은 바로 미래의 변화를 어떻게 대비할 것인가이다.
- 미래를 위해 설계하고 개발하는 덕분에 미래에 닥칠지도 모르는 거대한 작업에 대한 부담과 변경에 따른 엄청난 스트레스, 그로 인해 발생하는 고객과의 사이에서 또 개발팀 내에서의 갈등을 최소화할 수 있다.
- 객체지향 설계와 프로그래밍이 이전의 절차적 프로그래밍 패러다음에 비해 초기에 좀 더 많은, 번거로운 작업을 요구하는 이유는 객체지향 기술 자체가 지니는 변화에 효과적으로 대처할 수 있다는 기술적인 특징 때문이다.
- 객체지향기술은 흔히 실세계를 최대한 가깝게 모델링해낼 수 있기 때문에 의미가 있다고 여겨진다.
- 객체지향 기술이 만들어내는 가상의 추상세계 자체를 효과적으로 구성할 수 있고, 이를 자유롭고 편리하게 변경, 발전, 확장시킬 수 있다는 데 더 의미가 있다.
- 변화가 한 번에 한 가지 관심에 집중돼서 일어난다면, 우리가 준비해야 할 일은 한 가지 관심이 한 군데에 집중되게 하는 것이다. 즉, 관심이 같은 것끼리는 모으고 관심이 다른 것은 따로 떨어져 있게 하는 것이다.
- 프로그래밍의 기초 개념 중 관심사의 분리. → 객체지향에 적용
- 관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모이게 하고, 관심이 다른 것은 가능한 한 따로 떨어져서 서로 영향을 주지 않도록 분리하는 것
- 객체지향의 세계에서는 모든 것이 변한다.
- 커넥션 만들기의 추출
- add() 메소드 하나에서 적어도 세 가지 관심사항 발견
- UserDao의 관심사항
- DB와 연결을 위한 커넥션을 어떻게 가져올까.
- 어떤 DB를 쓰고 어떤 드라이버를 사용할 것이고, 어떤 로그인 정보를 쓰는데, 그 커넥션을 생성하는 방법은 또 어떤 것이다 라는 것까지 구분해서 볼 수도 있음.
- 일단은 뭉뚱그려서 DB연결과 관련된 관심이 하나라고 보자. 이것만 해도 여타 관심사항과는 명확히 구분된다.
- 사용자 등록을 위해 DB에 보낼 SQL 문장을 담을 Statement를 만들고 실행하는 것.
- 여기서의 관심은 파라미터로 넘어온 사용자의 정보를 Statement에 바인딩시키고, Statement에 담긴 SQL을 DB를 통해 실행시키는 방법이다.
- 파라미터를 바인딩하는 것과 어떤 SQL을 사용할지를 다른 관심사로 분리할 수도 있기도 하지만, 우선은 이것도 하나로 묶어서 생각한다.
- 작업이 끝나면 사용한 리소스인 Statement와 Connection 오브젝트를 닫아줘서 소중한 공유 리소스를 시스템에 돌려주는 것.
- DB와 연결을 위한 커넥션을 어떻게 가져올까.
- 가장 문제가 되는 것은 첫째 관심사인 DB연결을 위한 Connection 오브젝트를 가져오는 부분이다.
- 현재 DB 커넥션을 가져오는 코드는 다른 관심사와 섞여서 같은 add() 메소드에 담겨져 있다.
- 더 큰 문제는 add() 메소드에 있는 DB 커넥션을 가져오는 코드와 동일한 코드가 get() 메소드에 중복되어 있다는 점이다.
- 중복 코드의 메소드 추출
- 중복된 DB 연결 코드를 getConnection()이라는 이름의 독립적인 메소드로 만들어준다.
- 각 DAO 메소드에서는 이렇게 분리한 getConnection() 메소드를 호출해서 DB 커넥션을 가져오게 만든다.
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public void get(User id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
// 중복된 코드를 독립적인 메소드로 만들어서 중복을 제거했다.
// DB 연결 기능이 필요하면 getConnection() 메소드를 이용하게 한다.
public void getConnection() throws ClassNotFoundException, SQLException {
class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mtsql://localhost/springbook", "spring", "book");
return c;
}
- 변경사항에 대한 검증 : 리팩토링과 테스트
- 리팩토링
- 겉으로 드러나는 기능은 그대로이지만, 코드 구조와 구현 방법을 바꿈으로써 더 나은 DAO를 만드는 작업
- 기존의 코드를 외부의 동작방식에는 변화 없이 내부 구조를 변경해서 재구성하는 작업 또는 기술을 말한다.
- 리팩토링을 하면 코드 내부의 설계가 개선되어 코드를 이해하기가 더 편해지고 변화에 효율적으로 대응할 수 있다.
- 결국, 생산성은 올라가고, 코드의 품질은 높아지며, 유지보수하기 용이해지고, 견고하면서도 유연한 제품을 개발할 수 있다.
- 나쁜 냄새 (bad smell) → 리팩토링이 절실히 필요한 코드의 특징
- 중복된 코드
- 메소드 추출
- 리팩토링
- DB 커넥션 만들기의 독립
- 상속을 통한 확장
- UserDao에서 메소드의 구현 코드 제거
- getConnection()을 추상 메소드로 만든다.
- 추상 메소드라서 메소드 코드는 없지만 메소드 자체는 존재한다.
public class NUserDao extends UserDao { // 상속을 통해 확장된 getConnection() 메소드 public abstract Connection getConnection() throws ClassNotFoundException, SQLException { // N사 DB connection 생성코드 ... } } public class DUserDao extends UserDao { // 상속을 통해 확장된 getConnection() 메소드 public abstract Connection getConnection() throws ClassNotFoundException, SQLException { // D사 DB connection 생성코드 ... } } - 이렇게 슈퍼 클래스에 기본적인 로직의 흐름(커넥션 가져오기, SQL 생성, 실행, 반환)을 만들고 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤 서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법을 디자인 패턴에서 “템플릿 메소드 패턴” 이라고 한다.
- UserDao의 getConnection() 메소드는 Connection 타입 오브젝트를 생성한다는 기능을 정의해놓은 추상 메소드다. 그리고 UserDao의 서브 클래스의 getConnection() 메소드는 어떤 Connection 클래스의 오브젝트를 어떻게 생성할 것인지를 결정하는 방법이라고도 볼 수 있다. 이렇게 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것을 “팩토리 메소드 패턴”이라고 부르기도 한다.
- 서브 클래스에서 구현하는 getConnection() 메소드는 JDBC가 정의한 Connection 인터페이스를 구현한 Connection 오브젝트를 각자의 생성 알고리즘을 이용해 만들어낸다.
- 상속을 통한 확장
“UserDao에 팩토리 메소드 패턴을 적용해서 getConnection() 을 분리합시다.”
-
- 앞의 설명이 이 한마디에 담아진다.
- 템플릿 메소드 패턴
- 상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법
- 슈퍼클래스 - 변하지 않는 기능 (템플릿 메소드)
- 추상 메소드 정의
- 오버라이드 가능한 메소드 정의
- 훅 (hook)
- 슈퍼클래스에서 디폴트 기능을 정의해두거나 비워뒀다가 서브클래스에서 선택적으로 override할 수 있도록 만들어둔 메소드
- 서브클래스 - 자주 변경되며 확장할 기능
- 추상 메소드 구현
- 훅 메소드 오버라이드
public abstract class Super { public void templateMethod() { // 기본 알고리즘 코드 hookMethod(); abstractMethod(); /** 기본 알고리즘 골격을 담은 메소드를 템플릿 메소드라 부른다. 템플릿 메소드는 서브클래스에서 오버라이드 하거나, 구현할 메소드를 사용한다. */ ... } // 선택적으로 오버라이드 가능한 훅 메소드 protected void hookMethod() { } // 서브클래스에서 반드시 구현해야 하는 추상 메소드 public abstract void abstractMethod(); } // 슈퍼 클래스의 메소드를 오버라이드하거나 구현해서 기능을 확장한다. // 다양한 확장 클래스를 만들 수 있다. public class Sub1 extends Super { protected void hookMethod() { ... } public abstract void abstractMethod() { ... } }
- 팩토리 메소드 패턴
- 상속을 통해 기능을 확장
- 템플릿 메소드 패턴과 구조 비슷
- 슈퍼클래스 코드에서는 서브클래스에서 구현할 메소드를 호출해서 필요한 타입의 오브젝트를 가져와 사용
- 이 메소드는 주로 인터페이스 타입으로 오브젝트를 리턴
- 서브클래스에서 정확히 어떤 클래스의 오브젝트를 만들어 리턴할지는 슈퍼클래스에서는 알지 못한다.
- 서브 클래스는 다양한 방법으로 오브젝트를 생성하는 메소드를 재정이 할 수 있음.
- 서브클래스에서 오브젝트 생성 방법과 클래스를 결정할 수 있도록 미리 정의해둔 메소드를 팩토리 메소드라 한다.
- 이 방식을 통해 오브젝트 생성 방법을 나머지 로직, 즉, 슈퍼 클래스의 기본 코드에서 독립시키는 방법을 팩토리 메소드 패턴이라 함
- 자바에서는 종종 오브젝트를 생성하는 기능을 가진 메소드를 일반적으로 팩토리 메소드라고 부르기도 한다.
- 이때 말하는 팩토리 메소드와 팩토리 메소드 패턴의 팩토리 메소드는 의미가 다르므로 혼동하지 않도록 주의해야 함.
- 상속을 사용했다는 단점
- 상속 자체는 간단해 보이고 사용하기도 편리하게 느껴지지만, 사실 많은 한계가 존재
- 자바는 클래스의 다중상속을 허용하지 않음
- 상속을 통한 상하위 클래스의 관계는 생각보다 밀접
- 상속을 통해 관심이 다른 기능을 분리하고, 필요에 따라 다양한 변신이 가능하도록 확장성도 줬지만 여전히 상속관계는 두 가지 다른 관심사에 대해 긴밀한 결함을 허용
- 서브 클래스는 슈퍼클래스의 기능을 직접 사용 가능
- 슈퍼클래스 내부의 변경이 있을 때 모든 서브 클래스를 함께 수정하거나 다시 개발해야 할 수도 있음.
- 반대로, 위와 같은 변화에 따른 불편을 주지 않기 위해 슈퍼클래스가 더 이상 변화하지 않도록 제약을 가해야 할지도 모른다.
🧸 Git
GitHub - Been2zZ/TIL_springboot_book
Contribute to Been2zZ/TIL_springboot_book development by creating an account on GitHub.
github.com
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday