티스토리 뷰

1장 오브젝트와 의존관계

1.1 초난감 DAO

  • DAO
    • DAO(Data Access Object)는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트를 말한다.
  • 자바빈
    • 자바빈(JavaBean)은 원래 비주얼 툴에서 조작 가능한 컴포넌트를 말한다.
    • 자바의 주력 개발 플랫폼이 웹 기반의 엔터프라이즈 방식으로 바뀌면서 비주얼 컴포넌트로서 자바빈은 인기를 잃어갔지만, 자바빈의 몇 가지 코딩 관례는 JSP 빈, EJB와 같은 표준 기술과 자바빈 스타일의 오브젝트를 사용하는 오픈소스 기술을 통해 계속 이어져 왔다.
    • 이제는 자바빈이라고 말하면 비주얼 컴포넌트라기보다는 다음 두가지 관례를 따라 만들어진 오브젝트를 가리킨다. 간단히 빈(Bean)이라고 부르기도 한다.
      • 디폴트 생성자 : 자바빈은 파라미터가 없는 디폴트 생성자를 갖고 있어야 한다. 툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문에 필요하다.
      • 프로퍼티 : 자바빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는 set으로 시작하는 수정자 메소드(setter)와 get으로 시작하는 접근자 메소드(getter)를 이용해 수정 또는 조회할 수 있다.
  • 사용자 정보를 DB에 넣고 관리할 수 있는 DAO 클래스
    • JDBC를 이용하는 작업의 일반적인 순서
      1. DB 연결을 위한 Connection을 가져온다.
      2. sql을 담은 Statement(또는 PreparedStatement)를 만든다.
      3. 만들어진 Statement를 실행한다.
      4. 조회의 경우 SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 오브젝트(User)에 옮겨준다.
      5. 작업 중에 생성된 Connection, Statement, ResultSet 같은 리소스는 작업을 마친 후 반드시 닫아준다.
      6. JDBC API가 만들어내는 예외를 잡아서 직접 처리하거나, 메소드에 throws를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다.
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의 관심사항
      1. DB와 연결을 위한 커넥션을 어떻게 가져올까.
        • 어떤 DB를 쓰고 어떤 드라이버를 사용할 것이고, 어떤 로그인 정보를 쓰는데, 그 커넥션을 생성하는 방법은 또 어떤 것이다 라는 것까지 구분해서 볼 수도 있음.
        • 일단은 뭉뚱그려서 DB연결과 관련된 관심이 하나라고 보자. 이것만 해도 여타 관심사항과는 명확히 구분된다.
      2. 사용자 등록을 위해 DB에 보낼 SQL 문장을 담을 Statement를 만들고 실행하는 것.
        • 여기서의 관심은 파라미터로 넘어온 사용자의 정보를 Statement에 바인딩시키고, Statement에 담긴 SQL을 DB를 통해 실행시키는 방법이다.
        • 파라미터를 바인딩하는 것과 어떤 SQL을 사용할지를 다른 관심사로 분리할 수도 있기도 하지만, 우선은 이것도 하나로 묶어서 생각한다.
      3. 작업이 끝나면 사용한 리소스인 Statement와 Connection 오브젝트를 닫아줘서 소중한 공유 리소스를 시스템에 돌려주는 것.
    • 가장 문제가 되는 것은 첫째 관심사인 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