[JDBC] 2. Refactoring, DesignPattern

Nam_JU·2022년 7월 23일
0

KaKao Cloud School

목록 보기
15/19


LayerdArchitecture

이전시간에는 LayerdArchitecture을 배웠다. 한 클래스에서 데이터베이스 연동, 비즈니스 로직처리 등 모든것을 넣지 않고 층으로 나누어 작업하는 방식이다. 각각의 계층에는 서로 영향을 받지 않도록 한다

SoC (Seperation of Concern)

  • 클래스를 작성할때 주의를 기울어야 하는 이유는 유지보수 때문이다
  • 어떤 변화가 있을때 코드를 쉽고 빠르게 안정적으로 수정할 수 있어야 한다
  • 코드의 수정이 다른부분에 영향을 주는것을 최소화 시켜야 한다 ⇒ SoC

Step01_ 리펙토링하기

Refactoring 리펙토링

결과의 변경 없이 코드의 구조를 재조정함
1. 주로 가독성을 높이고 유지보수를 편하게 한다.
2. 버그를 없애거나 새로운 기능을 추가하는 행위는 아니다.
3. 사용자가 보는 외부 화면은 그대로 두면서 내부 논리나 구조를 개선하는 유지보수 행위이다.

  • 리펙토링 전
public class UserDAO {
    //todo: 새로운 사용자 등록 + id 값을 가지고 사용자 검색
    /**사용자 등록 - User라는 클래스 객체를 받아 뽑아서 사용*/
    // 외부 리소스를 사용하기 때문에 예기치못할 에러를 잡기위해 try-catch 가 필요하다 하지만 그때마다 사용하기 힘듬 => throw
    public void insert(User user) throws ClassNotFoundException, SQLException{
//1. driver 로드
        Class.forName("com.mysql.cj.jdbc.Driver"); //forName: drive 부터 로딩한다
//2. 데이터베이스 연결결
        String jdbc_URL = "jdbc:mysql://localhost:3306/sqldb?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true";
        Connection con = DriverManager.getConnection(jdbc_URL,"root","kim8480848");
        String sql = "INSERT INTO users VALUES(?,?,?)";
        //3. SQL을 위한 객체 생성 => 구문해석 X 전달 역할
        PreparedStatement pstmt = con.prepareStatement(sql);
        //4. 불러온 값의 결과 처리
        pstmt.setString(1, user.getId());
        pstmt.setString(2, user.getName());
        pstmt.setString(3, user.getPassword());
       pstmt.executeUpdate(); //update - insert, update, delete
        pstmt.close();
        con.close();
    }
        //vo 객체 하나 만들어서 리턴 => 받은 값을 데이터베이스에서 찾고 => vo 객체화 하여 리턴한다
    public User select(String id) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver"); //forName: drive 부터 로딩한다
        String jdbc_URL = "jdbc:mysql://localhost:3306/sqldb?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true";
        Connection con = DriverManager.getConnection(jdbc_URL,"root","kim8480848");
        String sql = "SELECT * FROM users WHERE id = ?";
        PreparedStatement pstmt = con.prepareStatement(sql);
        pstmt.setString(1, id);
        ResultSet rs = pstmt.executeQuery(); //select 만
        rs.next(); //데이터 당기기
       //VO 만들기
        User user = new User();
        user.setId(rs.getString("id"));
        user.setName(rs.getString("name"));
        user.setPassword(rs.getString("password"));
        rs.close();
        pstmt.close();
        con.close();
        return user;
    }
}
  • 리펙토링 후
    메소드마다 들어있던 JDBC연결 작업들을 하나의 메소드로 묶었다

Step02_ Template Method Pattern & Factory Method Pattern

Template Method Pattern 템플릿 메소드 패턴

  • 클래스를 상속하여 확장하는 가장 기본적인 방식이다
  • 변경되지 않는 기능은 상위클래스
  • 변경되어야 하는부분은 하위 클래스에서 구현한다
    • 장점

      1. 중복코드를 줄일 수 있다.
      2. 자식 클래스의 역할을 줄여 핵심 로직의 관리가 용이하다.
      3. 좀더 코드를 객체지향적으로 구성할 수 있다.

    • 단점

      1. 추상 메소드가 많아지면서 클래스 관리가 복잡해진다.
      2. 클래스간의 관계와 코드가 꼬여버릴 염려가 있다.

  • example
    위의 리펙토링 방식으로 좋은 코드를 만들었고 판매를 하고싶다는 가정을 해보자. 현재의 방식으로는 JDBC를 연결하기 위한 내 방식에 회사들이 맞춰야 한다. 또한 내가 준 코드를 그대로 줘야 회사들이 변경가능하다. 애플이 맥북을 파는게 아닌 맥북 설계도를 파는 꼴이 된다
    사용자 편의성을 높이기 위해 어떻게 해야할까?

  1. 내가 팔고자 하는 코드는 추상클래스로 만든다.
    JDBC연결로직을 추상메소드로 만드는 것이다.
    즉, 각각의 회사들이 JDBC연결 추상메소드를 하위클래스로 오버라이딩하여 변경 가능하도록 확장시켜준다
  • 보안을 위해 protected로 변경
    같은 패키지 내에서만 사용 가능하도록 추상 메소드를 protected하였다


  1. 하위코드
    사고싶은 로직은 그대로 둔채 자신의 JDBC연결 부분만 상속받아 확장시켰다


Factory Method Pattern 팩토리 메소드 패턴

  • 위의 코드 방식은 팩토리 메소드 패턴이기도 하다
  • 객체가 생성되는 관점으로 보면 하위 클래스에서 객체를 생성한다
    상위클래스에서 객체를 만들기는 하지만 객체를 만든다는 명시를 했어도 실제로 만드는 것은 하위클래스에서 오버라이딩한 getConnection()이다

Step03_ 상속의 문제 : 클래스 분리

위의 코드는 상속을 사용하여 해결하였다. 이 방식이 과연 항상 옳을까?

  • 자바는 단일상속을 지원한다
    • 네이버가 상속받아서 NUserDAO를 만드는데 만약 NUserDAO가 특별하게 또다른 클래스를 상속받아야 할 경우 반드시 UserDAO를 사용해야함. 이렇듯 단일상속에 대한 문제 때문에 고민해봐야한다
    • 상속을 이용하지 않고 이런 효과를 내려면?
  • 클래스를 분리해서 만든다
  • 수행은 잘 되지만 소스코드를 공개하지 않는이상 재사용하기 힘들다
  • UserDAO는 simpleConnectionMaker로 종속이 되버린다
  • 한 클래스 안에 다른 클래스의 이름이 명시적으로 박혀있으면 안좋다
  • 이번은 상속을 하지않고 JDBC연결 부분을 다른 클래스로 분리시킨다


  • 팔고싶은 코드의 클래스 생성자에 JDBC연결을 할수있는 클래스를 만들 수 있는 new 연산자를 사용한다.


  • main에서 UserDAO 클래스가 생성되는 순간 UserDAO 생성자가 호출되고 이때 JDBC연결 클래스도 만들어진다


  • 왜 필드에 선언하냐고?
    메소드가 호출 될 때마다 인스턴스를 생성해야하며 코드가 더럽고 메모리 낭비를 하게된다

Step3의 한계

클래스를 분리하여 사용하는것 까지는 좋았으나 소스코드를 공개하지 않는 이상 재사용이 힘들다. UserDAO 생성자 안에 JDBC연결 클래스(SimpleConnectionMakerclass)를 넣음으로서 종속되었고 두 클래스가 Tigtly Coupled 강하게 결합되었다. 오히려 첫번째 보다 안좋은 상황

  • 한 클래스 안에 다른 클래스 이름이 박혀 있으면 안된다!

Step04_Interface 인터페이스

인터페이스
실제 구현체의 메소드를 사용할 수 있다

  • 인터페이스 생성


  • 코드를 사가는 회사의 클래스가 해당 인터페이스를 상속받아서 JDBC연결을 한다


  • 팔고자하는 코드

    여기서 문제는 인터페이스 자체로는 객체를 만들수 없기 때문에 인터페이스를 구현한 클래스가 UserDAO클래스에 있어야 객체생성이 가능해진다
    아이러니하게도 팔기도 전에 회사의 JDBC연결 코드가 있어야 성립이 가능
    또한 클래스안에 다른 클래스의 이름이 명시됨으로서 연결이 타이트해졌다

어떻게 해결해야 할까?


Step05_DI:Dependency Injection

위의 코드의 문제는 아직 팔지도 않은 회사의 JDBC 연결 클래스를 생성하여
팔고자하는 클래스 (UserDAO)에 직접 생성하여 사용할 수 밖에 없었다
클래스안에 클래스가 들어가 결합도가 높아짐으로
이번에는 인터페이스 자체를 UserDAO 생성자의 매개변수에 주입해버렸다


그렇다면 인터페이스로 구현된 구현체 클래스(코드를 사고자하는 회사의 JDBC연결 코드)는 어떻게 생성해야할까?

  • 나중에 인터페이스를 구현하여 구현체가 생성될것이다
  • 이것을 연결해줄 코드는 Main에 생성
    UserDAO를 만드려면 인터페이스 타입의 구현체를 생성하고 팔고자 하는 코드에 주입을 시켜준다!

이로서 클래스 의존성이 아닌 객체의 의존성을 높여서 코드를 생성했다



참고자료
https://velog.io/@rlrhs11/Code-Refactoring코드-리펙토링-이란
https://western-sky.tistory.com/40

profile
개발기록

0개의 댓글