1장-오브젝트와 의존관계
1장 오브젝트와 의존관계
스프링은 자바
를 기반으로 한 기술이다.
스프링이 가장 관심을 많이 두는 대상은 오브젝트다
.
애플리케이션에서 오브젝트가 생성되고 다른 오브젝트와 관계를 맺고, 사용되고, 소멸하기까지의 전 과정
을 진지하게 생각해볼 필요가 있다.
추가로, 오브젝트는 어떻게 설계
돼야 하는지, 어떤 단위로 만들어지며
어떤 과정을 통해 자신의 존재를 드러내고 등장해야 하는지
에 대해서도 살펴봐야한다.
스프링은 오브젝트를 어떻게 효과적으로 설계하고 구현하고, 사용하고, 이를 개선해나갈 것인가
에 대한 명쾌한 기준을 마련해준다.
1장에서는 스프링이 관심을 갖는 대상
인 오브젝트의 설계와 구현
, 동작원리
에 더 집중하기를 바란다.
그러다보면 자연스럽게 스프링이 무엇인지도 이해될 것이다.
1.1 초난감 DAO
DAO란 ?
- DAO(Data Access Object)는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트를 말한다.
자바빈이란?
자바빈(JavaBean)
은 다음 두가지 관례를 따라 만들어진 오브젝트를 가리킨다.디폴트 생성자
: 자바빈은파라미터가 없는 디폴트 생성자
를 갖고 있어야 한다.툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성
하기 때문에 필요하다.프로퍼티
: 자바빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는set
으로 시작하는수정자 메소드(setter)
와get
으로 시작하는접근자 메소드(getter)
를 이용해수정 또는 조회
할 수 있다.
1.1.1 User
사용자 정보를 JDBC API를 통해 DB에 저장하고 조회할 수 있는 간단한 DAO를 하나 만들어본다.
사용자 정보를 저장할 User 클래스를 만든다.
@Setter
@Getter
public class User {
private String id;
private String name;
private String password;
}
1.1.2 UserDao
사용자 정보를 DB에 넣고 관리할 수 있는 DAO 클래스를 만들어본다.
- 새로운 사용자를 생성하고(add) 메소드 만들기
- 아이디를 가지고 사용자 정보를 읽어오는(get) 메소드 만들기
JDBC를 이용하는 작업의 일반적인 순서
- DB 연결을 위한 Connection을 가져온다.
- SQL을 담은 Statement(또는 PreparedStatement)를 만든다.
- 만들어진 Statement를 실행한다.
- 조회의 경우 SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 오브젝트(여기서는 User)에 옮겨준다.
- 작업 중에 생성된 Connection, Statement, ResultSet 같은 리소스는 작업을 마친 후 반드시 닫아준다.
- JDBC API가 만들어내는 예외를 잡아서 직접 처리하거나, 메소드에 throws를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다.
UserDao: 새로운 사용자를 생성하는 메서드, 아이디를 가지고 사용자 정보를 읽어오는 메서드
public class UserDao {
public void add(User user) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/user-db", "root", "0000");
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.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/user-db", "root", "0000");
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;
}
}
1.1.3 main을 이용한 DAO 테스트 코드
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
UserDao dao = new UserDao();
User user = new User();
user.setId("diamond");
user.setName("장종욱");
user.setPassword("1234");
dao.add(user);
System.out.println(user.getId() + "등록 성공");
User user2 = dao.get(user.getId());
System.out.println(user2.getName());
System.out.println(user2.getPassword());
System.out.println(user2.getId() + "조회 성공");
}
}
1.2 DAO의 분리
1.2.1 관심사의 분리
- 개발자가 객체를 설계할 때 가장 염두에 둬야할 사항은 바로
미래의 변화를 어떻게 대비할 것인가
이다. -
객체지향 설계와 프로그래밍이 초기에 좀 더 많은, 번거로운 작업을 요구하는 이유는 객체지향 기술 자체가 지니는,
변화에 효과적으로 대처할 수 있다는 기술적인 특징
때문이다. - 어떻게 변경이 일어날 때
필요한 작업을 최소화
하고,그 변경이 다른 곳에 문제를 일으키지 않게
할 수 있을까? -
그것은
분리와 확장
을 고려한 설계가 있었기 때문이다. - 문제는, 변화는 대체로 집중된 한가지 관심에 대해 일어나지만 그에 따른 작은은 한곳에 집중되지 않는 경우가 많다는 점이다.
- 프로그래밍의 기초 개념 중에
관심사의 분리
라는게 있다. - 관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모이게 하고, 관심이 다른 것은 가능한 한 따로 떨어져서 서로 영향을 주지 않도록 분리하는 것이다.
관심사가 같은 것끼리 모으고 다른 것은 분리
해줌으로써같은 관심에 효과적으로 집중할 수 있게 만들어주는 것
이다.
1.2.2 커넥션 만들기의 추출
UserDAO의 관심사항
- DB와 연결을 위한 커넥션을 어떻게 가져올까라는 관심
- 사용자 등록을 위해 DB에 보낼 SQL 문장을 담을 Statement를 만들고 실행하는 것
- 작업이 끝나면 사용한 리소스인 Statement와 Connection 오브젝트를 닫아줘서 소중한 공유 리소스를 시스템에 돌려주는 것
- 그러나, 가장 문제가 되는 것은 첫째 관심사인 DB연결을 위한 Connection 오브젝트를 가져오는 부분이다.
- 여기저기 흩어져 있어서 다른 관심의 대상과 얽혀 있으면, 변경이 일어날 때 엄청난 고통을 일으키는 원인이 된다.
중복 코드의 메소드 호출
- 가장 먼저 할일은
커넥션을 가져오는 중복된 코드를 분리하는 것
이다. - 각 DAO 메서드에서는 이렇게 분리한 getConnection() 메서드를 호출해서 DB 커넥션을 가져오게 만든다.
** getConnection() 메서드를 추출해서 중복을 제거한 UserDao
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
// DB 연결 기능이 필요하면 getConnection() 메서드를 이용하게 한다.
...
}
// 중복된 코드를 독립적인 메서드로 만들어서 중복을 제거했다.
private Connection getConnection() throws ClassNotFoundException, SQLException {
Clss.forName("com.mysql.jdbc.Driver");
Connnection c = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/schema", "spring", "book");
return c;
}
관심의 종류에 따라 코드를 구분
해놓았기 때문에한 가지 관심에 대한 변경이 일어날 경우 그 관심이 집중되는 부분의 코드만 수정
하면 된다.- 예를 들어, 위와 같이 DB 종류와 접속 방법이 바뀌어서 드라이버 클래스와 URL이 바뀌었다거나, 로그인 정보가 변경돼도 앞으로는 getConnection()이라는 한 메소드의 코드만 수정하면 된다.
변경사항에 대한 검증: 리팩토링과 테스트
- 코드 수정한 후에 기능에 문제가 없다는게 보장되지 않기에 검증이 필요하다.
- main() 메소드를 이용한 테스트를 실행해본다.
리팩토링
- 리팩토링은 기존의 코드를 외부의 동작방식에는 변화 없이 내부 구조를 변경해서 재구성하는 작업 또는 기술을 말한다.
- 리팩토링을 하면 코드 내부의 설계가 개선되어 코드를 이해하기가 더 편해지고, 변화에 효율적으로 대응할 수 있다.
-
생산성은 올가고, 코드의 품질은 높아지며, 유지보수하기 용이해지고, 견고하면서도 유연한 제품을 개발할 수 있다.
메소드추출기법
- 위에서 사용한 getConnection()이라고 하는
공통의 기능을 담당하는 메소드로 중복된 코드를 뽑아내는 것
을 리팩토링에서는 메소드 추출 기법이라고 부른다.
1.2.3 DB 커넥션 만들기의 독립
상속을 통한 확장
- 기존 UserDao 코드를 한 단계 더 분리하면 된다.
- 즉, UserDao에서 메소드의 구현 코드를 제거하고 getConnection()을 추상 메소드로 만들어놓는다.
템플릿 메소드 패턴
- 슈퍼클래스에 기본적인 로직의 흐름(커넥션 가져오기, SQL 생성, 실행, 반환)을 만들고,
- 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤 서브클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 디자인 패턴
- 즉, 슈퍼클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법
- 변하지 않는 기능은 슈퍼클래스에 만들어두고 자주 변경되며 확장할 기능은 서브클래스에서 만들도록 한다.
훅(hook) 메서드
란 슈퍼클래스에서 디폴트 기능을 정의해두거나 비워뒀다가 서브클래스에서 선택적으로 오버라이드할 수 있도록 만들어둔 메소드
팩토리 메소드 패턴
UserDao의 getConnection() 메소드
는 Connection 타입 오브젝트를 생성한다는 기능을 정의해놓은 추상 메소드다.UserDao의 서브클래스의 getConnection() 메소드
는 어떤 Connection 클래스의 오브젝트를 어떻게 생성할 것인지를 결정하는 방법이다.- 이렇게 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것을
팩토리 메소드 패턴
이라고 부른다. - 즉, 서브클래스는 다양한 방법으로 오브젝트를 생성하는 메소드를 재정의할 수 있다.
- 이렇게 서브클래스에서 오브젝트 생성 방법과 클래스를 결정할 수 있도록 미리 정의해둔 메소드
상속을 통한 확장 방법이 제공되는 UserDao
(참고) 자바는 클래스의 다중상속을 허용하지 않는다.
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;
}
public class NUserDao extends UserDao {
public Connection getConnection() throws ClassNotFoundException, SQLException {
// N사 DB connection 생성 코드
}
}
public class DUserDao extends UserDao {
public Connection getConnection() throws ClassNotFoundException, SQLException {
// D 사 DB connection 생성 코드
}
}
디자인 패턴
-
소프트웨어 설계 시 특정 상황에서 자주 만나는 문제를 해결하기 위해 사용할 수 있는 재사용 가능한 솔루션
- 디자인 패턴은 주로 객체지향 설계에 관한 것이고, 대부분 객체 지향적 설계 원칙을 이용해 문제를 해결한다.
- 객체지향적인 설계로부터 문제를 해결하기 위해 적용할 수 있는 확장성 추구 방법이 대부분 2가지 구조로 정리된다.
- 하나는
클래스 상속
이고 다른 하나는오브젝트 합성
이다. - 패턴에서 가장 중요한 것은 각 패턴의 핵심이 담긴 목적 또는 의도다.
- 디자인 패턴은 객체지향 언어인 자바를 사용하는 개발자라면 반드시 공부해야할 주제다.
- 디자인 패턴을 최초로 집대성한 책인
GoF의 디자인 패턴(에릭 감마 외)
또는Head First Design Patterns(에릭 프리먼)
을 추천한다.
1.3 DAO의 확장
- 변화의 성격은 변화의 이유와 시기, 주기 등이 다르다는 것이다.
- 이를 위해, 추상클래스를 만들고 이를 상속한 서브클래스에서 변화가 필요한 부분을 바꿔서 쓸 수 있게 만든 이유는 바로 이렇게 변화의 성격이 다른 것을 분리해서, 서로 영향을 주지 않은 채로 각각 필요한 시점에 독립적으로 변경할 수 있게 하기 위해서이다.
여러가지 단점이 많은, 상속이라는 방법
을 사용했다는 사실이 불편하다.
1.3.1 클래스의 분리
-
DB 커넥션과 관련된 부분을 서브클래스가 아니라, 아예 별도의 클래스에 담는다.
- 각 메소드에서 매번 SimpleConnectionMaker의 오브젝트를 만들수도 있지만, 그보다는 한번만 SimpleConnectionMaker 오브젝트를 만들어서 저장해두고 이를 계속 사용하는 편이 낫다.
- UserDao는 상속을 통한 방법을 쓰지 않으니 더이상 abstract일 필요는 없다.
생성자에서 SimpleConnectionMaker의 오브젝트를 만들어두고, add/get 메소드에서 이를 이용해 DB 커넥션을 가져오면 된다.
독립된 SimpleConnectionMaker를 사용하게 만든 UserDao
public class UserDao {
private SimpleConnectionMaker simpleConnectionMaker;
public UserDao() {
simpleConnectionMaker = new SimpleConnectionMaker();
// 상태를 관리하는 것도 아니니 한번만 만들어 인스턴스 변수에 저장해두고 메소드에서 사용하게 한다.
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = simpleConnectionMaker.makeNewConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = simpleConnectionMaker.makeNewConnection();
}
}
독립시킨 DB 연결 기능인 SimpleConnectionMaker
public class SimpleConnectionMaker {
public Connection makeNewConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdcbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/schema", "root", "0000");
return c;
}
}
1.3.2 인터페이스의 도입
추상화
- 추상화란 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업이다.
-
자바가 추상화를 위해 제공하는 가장 유용한 도구는 바로
인터페이스
이다. - 인터페이스는
어떤 일을 하겠다는 기능만 정의
해놓은 것이다. - 따라서
인터페이스에는 어떻게 하겠다는 구현 방법은 나타나 있지 않다.
ConnectionMaker 인터페이스
ConnectionMaker 인터페이스 정의
- DB 커넥션을 가져오는 메소드 이름은 makeConnection()이라고 정했다.
- makeConnectoin() 메소드를 호출하기만 하면 Connection 타입의 오브젝트를 만들어서 돌려줄것이라고 기대할 수 있다.
public interface ConnnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException;
}
- D 사의 개발자라면 ConnectionMaker 인터페이스를 구현한 클래스를 만들고, 자신들의 DB 연결 기술을 이용해 DB 커넥션을 가져오도록 메소드를 작성해준다.
ConnectionMaker 구현 클래스
public class DConnectionMaker implements ConnectionMaker {
...
public Connection makeConnection() throws ClassNotFoundException, SQLException{
// D 사의 독자적인 방법으로 Connection을 생성하는 코드
}
}
- 특정 클래스 대신 인터페이스를 사용해서 DB 커텍션을 가져와 사용하도록 수정한 UserDao 코드
ConnectionMaker 인터페이스를 사용하도록 개선한 UserDao
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();
...
}
}
댓글남기기