이전시간에는 LayerdArchitecture을 배웠다. 한 클래스에서 데이터베이스 연동, 비즈니스 로직처리 등 모든것을 넣지 않고 층으로 나누어 작업하는 방식이다. 각각의 계층에는 서로 영향을 받지 않도록 한다
결과의 변경 없이 코드의 구조를 재조정함
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연결 작업들을 하나의 메소드로 묶었다
장점
1. 중복코드를 줄일 수 있다.
2. 자식 클래스의 역할을 줄여 핵심 로직의 관리가 용이하다.
3. 좀더 코드를 객체지향적으로 구성할 수 있다.
단점
1. 추상 메소드가 많아지면서 클래스 관리가 복잡해진다.
2. 클래스간의 관계와 코드가 꼬여버릴 염려가 있다.
- example
위의 리펙토링 방식으로 좋은 코드를 만들었고 판매를 하고싶다는 가정을 해보자. 현재의 방식으로는 JDBC를 연결하기 위한 내 방식에 회사들이 맞춰야 한다. 또한 내가 준 코드를 그대로 줘야 회사들이 변경가능하다. 애플이 맥북을 파는게 아닌 맥북 설계도를 파는 꼴이 된다
사용자 편의성을 높이기 위해 어떻게 해야할까?
- 내가 팔고자 하는 코드는 추상클래스로 만든다.
JDBC연결로직을 추상메소드로 만드는 것이다.
즉, 각각의 회사들이 JDBC연결 추상메소드를 하위클래스로 오버라이딩하여 변경 가능하도록 확장시켜준다
- 보안을 위해 protected로 변경
같은 패키지 내에서만 사용 가능하도록 추상 메소드를 protected하였다
- 하위코드
사고싶은 로직은 그대로 둔채 자신의 JDBC연결 부분만 상속받아 확장시켰다
getConnection()
이다위의 코드는 상속을 사용하여 해결하였다. 이 방식이 과연 항상 옳을까?
- 이번은 상속을 하지않고 JDBC연결 부분을 다른 클래스로 분리시킨다
- 팔고싶은 코드의 클래스 생성자에 JDBC연결을 할수있는 클래스를 만들 수 있는 new 연산자를 사용한다.
- main에서 UserDAO 클래스가 생성되는 순간 UserDAO 생성자가 호출되고 이때 JDBC연결 클래스도 만들어진다
- 왜 필드에 선언하냐고?
메소드가 호출 될 때마다 인스턴스를 생성해야하며 코드가 더럽고 메모리 낭비를 하게된다
클래스를 분리하여 사용하는것 까지는 좋았으나 소스코드를 공개하지 않는 이상 재사용이 힘들다. UserDAO 생성자 안에 JDBC연결 클래스(SimpleConnectionMakerclass)를 넣음으로서 종속되었고 두 클래스가 Tigtly Coupled 강하게 결합되었다. 오히려 첫번째 보다 안좋은 상황
인터페이스
실제 구현체의 메소드를 사용할 수 있다
- 인터페이스 생성
- 코드를 사가는 회사의 클래스가 해당 인터페이스를 상속받아서 JDBC연결을 한다
- 팔고자하는 코드
여기서 문제는 인터페이스 자체로는 객체를 만들수 없기 때문에 인터페이스를 구현한 클래스가 UserDAO클래스에 있어야 객체생성이 가능해진다
아이러니하게도 팔기도 전에 회사의 JDBC연결 코드가 있어야 성립이 가능
또한 클래스안에 다른 클래스의 이름이 명시됨으로서 연결이 타이트해졌다
어떻게 해결해야 할까?
위의 코드의 문제는 아직 팔지도 않은 회사의 JDBC 연결 클래스를 생성하여
팔고자하는 클래스 (UserDAO)에 직접 생성하여 사용할 수 밖에 없었다
클래스안에 클래스가 들어가 결합도가 높아짐으로
이번에는 인터페이스 자체를 UserDAO 생성자의 매개변수에 주입해버렸다
그렇다면 인터페이스로 구현된 구현체 클래스(코드를 사고자하는 회사의 JDBC연결 코드)는 어떻게 생성해야할까?
- 나중에 인터페이스를 구현하여 구현체가 생성될것이다
- 이것을 연결해줄 코드는 Main에 생성
UserDAO를 만드려면 인터페이스 타입의 구현체를 생성하고 팔고자 하는 코드에 주입을 시켜준다!
이로서 클래스 의존성이 아닌 객체의 의존성을 높여서 코드를 생성했다
참고자료
https://velog.io/@rlrhs11/Code-Refactoring코드-리펙토링-이란
https://western-sky.tistory.com/40