본문 바로가기

Spring/토비의 스프링 3.1

1.2 DAO의 분리

1.2.1 관심사의 분리

객체 지향의 세계에서는 모든 것이 변한다.

  • 미래의 변화에 어떻게 대비할 것인가?
  • 변화의 폭을 최소한으로 줄여야한다.
  • 분리와 확장을 고려한 설계
  • 관심사의 분리(Separation of Concerns)

1.2.2 커넥션 만들기의 추출

UserDao의 관심사항

  1. DB와 연결을 위한 커넥션
  2. SQL 문장을 담은 Statement를 만들고 실행
  3. 작업이 끝난 후 Statement와 Connection 리소스 닫기
  4. 문제점
    • 관심사가 너무 많다.
    • add()메소드와 get()메소드에 DB커넥션을 가져오는 코드가 중복됨

중복 코드의 메소드 추출(메소드 추출기법)

  • 중복되는 코드를 독립적인 메소드로 추출 (getConnection)
  • DB 연결 방법이 바뀌면 getConnection() 메소드만 수정하면 된다.
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();
        // ...
    }

    private Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("oracle.jdbc.driver.OracleDriver");
        Connection c = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "spring", "book");

        return c;
    }
}

변경사항에 대한 검증

코드를 수정하게 되면 기능에 문제가 없음이 보장되지 않는다. 따라서 다시 한 번 검증이 필요하다.
main() 메서드를 만들어 테스트를 실행해보자.
테스트 실행 시 키 값 중복 오류가 발생하면 DB를 정리하고 다시 테스트한다.

public static void main(String[] args) throws ClassNotFoundException, SQLException{
    UserDao dao = new UserDao();

    User user = new User();
    user.setId("whiteship");
    user.setName("백기선");
    user.setPassword("married");

    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.3 DB 커넥션 만들기의 독립

상황
나의 DAO는 유명해졌다 !
N사와 D사에 납품을 하여야한다.
N사와 D사는 서로 다른 DB를 사용하고 있고, DB 커넥션을 가져오는 데 독자적인 방법을 쓰고 싶어한다.

이후, DB 커넥션 방법이 종종 변경될 가능성이 있다.
소스 코드는 공개하고 싶지 않다.

상속을 통한 확장

  • UserDao 코드를 한 번 더 분리
  • getConnection()을 추상 메소드로 분리
  • 메소드 코드는 없지만, 메소드 자체는 존재
  • add(), get() 메소드에서 getConnection() 호출 코드 유지 가능
  • N사와 D사는 NUserDao와 DUserDao라는 서브클래스를 만들어 getConnection()을 원하는대로 구현
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 static void main(String[] args) throws ClassNotFoundException, SQLException{
        NUserDao dao = new NUserDao();
        // ...
    }
}

// Oracle을 사용하는 N사의 UserDao
class NUserDao extends UserDao {
    @Override
    public Connection getConnection() throws ClassNotFoundException, SQLException{
        Class.forName("oracle.jdbc.driver.OracleDriver");
        Connection c = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "spring", "book");

        return c;
    }
}

// MySql을 사용하는 D사의 UserDao
class DUserDao extends UserDao {
    @Override
    public Connection getConnection() throws ClassNotFoundException, SQLException{
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");

        return c;
    }
}

관심사가 Class 레벨로 구분됨

  1. UserDao
    • DAO 핵심기능; 어떻게 데이터를 등록하고 가져올 것인가?
    • SQL문, 파라미터 바인딩, 쿼리 실행, 실행 결과 전달 등)
    • 어떤 기능을 사용한다는 데에만 관심
  2. NUserDao / DUserDao
    • DB 연결을 어떻게 할 것인가?
    • 어떤 식으로 Connection 기능을 제공하는지

변경과 확장에 유연해졌다. DB가 변경되더라도, 다른 DB를 추가하더라도 UserDao 코드는 수정할 필요가 없음

참고 : 디자인 패턴

소프트웨어 설계 시 특정 상황에서 자주 만나는 문제를 해결하기 위해 사용할 수 있는 재사용 가능한 솔루션을 의미함.

  • 대부분 객체 지향 설계에 관한 것이며, 객체 지향적 설계 원칙을 이용하여 문제를 해결
  • 확장성 추구 방법
    1. 클래스 상속
    2. 오브젝트 합성
  • 패턴의 결과로 나온 코드나 설계 구조는 대부분 비슷함
  • 참고자료 : "GoF 디자인 패턴"(에릭 감마 외) / "Head First Design Patterns"(에릭 프리먼)

템플릿 메소드 패턴(Template Method Pattern)

상속을 통해 슈퍼클래스의 기능을 확장할 때 가장 대표적 방법

  • 슈퍼클래스
    1. 변하지 않는 기능
    2. 기본적인 로직의 흐름(커넥션 가져오기, SQL 생성, 실행, 반환)
    3. 기능의 일부를 추상메서드나 오버라이딩 가능한 protected 메소드 등으로 추출
    4. 훅 메서드 : 선택적으로 구현(protected 메서드의 몸통을 비움)
    5. 추상 메서드 : 반드시 구현(abstract)
  • 서브클래스
    1. 자주 변경되며 확장할 기능
    2. 슈퍼클래스에서 남겨둔 메서드를 필요에 맞게 구현

팩토리 메소드 패턴(Factory Method Pattern)

  • 상속을 통해 기능을 확장하는 패턴으로 템플릿 메소드 패턴과 유사
  • 슈퍼클래스 코드에서는 서브클래스에서 구현할 메소드를 호출해서 필요한 타입의 오브젝트를 리턴
  • 서브클래스가 어떤 클래스의 오브젝트를 리턴할 지 알 수 없음
  • 팩토리 메서드 : 오브젝트 생성 방법과 클래스를 결정할 수 있도록 미리 정의해 둔 메서드
  • 슈퍼클래스의 기본 코드에서 독립

1.2 코드의 문제점

상속을 사용할 경우 여러가지 문제가 발생할 수 있다.

  • 다중상속을 허용하지 않는 자바에서 다른 목적으로 UserDao의 상속을 적용하기 힘듬
  • 상속 관계는 여저히 두 가지 다른 관심사에 대해 긴밀한 결합을 허용함
  • 확장된 기능인 DB 커넥션을 생성하는 코드를 다른 DAO 클래스에 적용할 수 없음
  • UserDao 외 다른 DAO가 계속 만들어지면 그 때마다 getConnection() 구현 코드가 중복됨

' Spring > 토비의 스프링 3.1' 카테고리의 다른 글

1.6 싱글톤 레지스트리와 오브젝트 스코프  (0) 2019.01.09
1.5 스프링의 IoC  (0) 2019.01.07
1.4 제어의 역전(IoC)  (0) 2019.01.07
1.3 DAO의 확장  (0) 2019.01.07
1.1 초난감 DAO  (0) 2019.01.03