1. 리팩토링
1) 클래스 추출
- 기존 클래스에서 필드와 메서드를 추출하여 새로운 클래스로 옮기는 것
2) 리팩토링 카탈로그
이름 | 클래스 추출(Extract Class) |
상황 | 클래스를 작성함 |
문제 | 한 클래스가 너무 많은 책임을 지고 있음 |
해법 | 묶을 수 있는 필드와 메서드를 찾아 새로운 클래스로 추출 |
결과 | o 클래스가 작아짐 o 클래스의 책임이 명확해짐 x 클래스의 개수가 늘어남 |
2. 예제 프로그램
클래스명 | 역할 |
Book | 책 클래스 |
Author | 저자 클래스(리팩토링으로 작성) |
Main | Book 사용법 예제 클래스 |
1) 리팩토링 전
public class Book {
private String _title;
private String _isbn;
private String _price;
private String _authorName;
private String _authorMail;
public Book(String title, String isbn, String price, String authorName, String authorMail) {
_title = title;
_isbn = isbn;
_price = price;
_authorName = authorName;
_authorMail = authorMail;
}
public String getTitle() { return _title; }
public String getIsbn() { return _isbn; }
public String getPrice() { return _price; }
public String getAuthorName() { return _authorName; }
public String getAuthorMail() { return _authorMail; }
public void setAuthorName(String name) { _authorName = name; }
public void setAuthorMail(String mail) { _authorMail = mail; }
public String toXml() {
String author =
tag("author", tag("name", _authorName) + tag("mail", _authorMail));
String book =
tag("book", tag("title", _title) + tag("isbn", _isbn) + tag("price", _price) + author);
return book;
}
private String tag(String element, String content) {
return "<" + element + ">" + content + "</" + element + ">";
}
// ...
}
- 리팩토링 대상 : 책 정보를 담는 클래스에서 저자 정보를 담는 클래스를 빼는 것
public class Main {
public static void main(String[] args) {
Book refactoring = new Book(
"Refactoring: improving the design of existing code",
"ISBN0201485672",
"$44.95",
"Martin Fowler",
"fowler@acm.org");
Book math = new Book(
"프로그래머 수학",
"ISBN4797329734",
"20000원",
"유키 히로시",
"hyuki@hyuki.com");
System.out.println("refactoring:");
System.out.println(refactoring.toXml());
System.out.println("math:");
System.out.println(math.toXml());
}
}
- 책 두 권의 정보를 XML형식으로 표시하였음
2) 리팩토링 실행
① 새로운 클래스 작성
⑴ 클래스의 책임을 어떻게 추출할지 결정
⑵ 추출한 책임을 담당할 새로운 클래스 작성
⑶ 기존 클래스에 새로운 클래스로 링크 작성
② 필드 이동
⑴ 기존 클래스에서 새로운 클래스로 필요한 필드 이동
⑵ 이동할 때마다 컴파일해서 테스트
③ 메서드 이동
⑴ 기존 클래스에서 새로운 클래스로 필요한 메서드 이동
⑵ 이동할 때마다 컴파일해서 테스트
④ 추출한 클래스 검토
⑴ 클래스 인터페이스를 줄일 수 있는가
- 불필요한 메서드를 삭제하거나 접근 제한을 private으로 변경하는 것이 좋음
⑵ 새로운 클래스를 외부에 공개해야 하는가
⑶ 공개한다면 외부에서 수정가능하게 할 것인가
3) 리팩토링 후
public class Book {
private String _title;
private String _isbn;
private String _price;
private Author _author;
public Book(String title, String isbn, String price, String authorName, String authorMail) {
_title = title;
_isbn = isbn;
_price = price;
_author = new Author(authorName, authorMail);
}
public String getTitle() {
return _title;
}
public String getIsbn() {
return _isbn;
}
public String getPrice() {
return _price;
}
public String getAuthorName() {
return _author.getName();
}
public String getAuthorMail() {
return _author.getMail();
}
public void setAuthorName(String name) {
_author.setName(name);
}
public void setAuthorMail(String mail) {
_author.setMail(mail);
}
public String toXml() {
String author =tag("author", tag("name", _author.getName()) + tag("mail", _author.getMail()));
String book =tag("book", tag("title", _title) + tag("isbn", _isbn) + tag("price", _price) + author);
return book;
}
private String tag(String element, String content) {
return "<" + element + ">" + content + "</" + element + ">";
}
class Author {
private String _name;
private String _mail;
public Author(String name, String mail) {
_name = name;_mail = mail;
}
public String getName() {
return _name;
}
public String getMail() {
return _mail;
}public void setName(String name) {
_name = name;
}public void setMail(String mail) {
_mail = mail;
}
}
}
3. 한 걸음 더 나아가기
1) 양방향 링크는 피한다
- 양방향 링크는 초기화나 관리가 복잡하고 필드나 메서드를 정리하기 어려움
- 하지만 어쩔수 없는경우엔 만들어야 함
(위 예제에선 Author 클래스에서 Book 클래스로 향한 링크가 없기 때문에 저자이름이 00인 책을 찾을 때 모든 book 객체들을 찾아봐야 한다. 반대로 book이름이 00인 저자는 링크가 있기때문에 쉽게 찾을 수 있음)
2) 기능 추가와 리팩토링
- 많은 기능을 하고 있는 클래스에서 클래스를 추출하면
- 기능 추가의 번잡함이 줄어듦
- 기능 추가가 더욱 적절한 클래스 안에서 이루어짐
3) 불변 인터페이스
- 불변 인터페이스 : 일반 인터페이스를 읽기 전용으로 만드는 것
- 예를 들어 Author 클래스 인스턴스를 읽기 전용으로 만들고 싶으면 아래와 같이 작성함
public interface ImmutableAuthor {
public String getName();
public String getMail();
//세터 메서드가 없다
}
public class Author implements ImmutableAuthor {
private String _name;
private String _mail;
public Author(String name, String mail) {
_name = name;
_mail = mail;
}
public String getName() { return _name; }
public String getMail() { return _mail; }
public void setName(String name) { _name = name; }
public void setMail(String mail) { _mail = mail; }
// ...
}
- 저자 정보를 변경하면 곤란한 경우에는 Author가 아닌 ImmutableAuthor 객체로 _author 필드 값을 넘김
public ImmutableAuthor getAuthor() {
return _author;
}
book.getAuthor().setName("..") => 컴파일 에러가 난다.
4) 역 리팩토링 : 클래스 인라인화
- 지나치게 클래스 추출하는 것도 문제기 때문에 작은 클래스를 큰 클래스로 합치는 클래스 인라인화가 있음
4. 정리
- 클래스에서 추출 가능한 책임을 찾은 후 그 책임과 관련 있는 필드와 메서드를 추출해서 새로운 클래스로 만듬
- 이 리팩토링으로 클래스 책임이 명확해짐
5. 연습문제
1) 어떤 클래스에 너무 많은 책임이 몰려 있다면 클래스 추출을 검토한다(O)
2) 클래스 추출을 한 클래스끼리는 양방향 링크를 거는 게 좋다(X)
3) 클래스 추출로 만든 새로운 클래스에는 내부를 어떻게 구현했는지 잘 알 수 있는 이름을 붙이는 게 좋다(X)
- 내부 구현이 아닌 해당 클래스의 책임을 알 수 있는 이름이 좋다.
'Major > Java' 카테고리의 다른 글
[자바로 배우는 리팩토링 입문] 8장 분류 코드를 하위 클래스로 치환 (0) | 2022.06.07 |
---|---|
[자바로 배우는 리팩토링 입문] 7장 분류 코드를 클래스로 치환 (0) | 2022.06.04 |
[자바로 배우는 리팩토링 입문] 5장 메서드 추출 (0) | 2022.06.01 |
[자바로 배우는 리팩토링 입문] 4장 널 객체 도입 (0) | 2022.04.15 |
[자바로 배우는 리팩토링 입문] 3장 어서션 도입 (0) | 2022.04.15 |