Form Template Method

각각의 서브 클래스에, 동일한 순서로 비슷한 단계를 행하지만 단계가 완전히 같지는 않은 두 메소드가 있다면,
그 단계를 동일한 시그너처를 가진 메소드로 만들어라.
이렇게 하면 원래의 두 메소드는 서로 같아지므로, 수퍼클래스로 올릴 수 있다.

동기
상속을 통해서 중복을 제거할 때 서브 클래스들에 있는 비슷한 메소드들을 본질적인 차이점만을 남겨놓고 중복을 제거할 수 있다. 이와 관련된 패턴으로 Template Method 패턴이 있다.

절차
1. 메소드를 분해해서 동일 한 부분과 다른 부분을 나눈다.
2. 동일한 부분을 Pull Up Method를 사용하여 수퍼클래스로 올린다.
3. 다른 부분을 정의한 메소드를 수퍼클래스에서 추상 메소드로 선언한다.
4. 서브 클래스에는 다른 부분을 정의한 메소드를 제외하고 모두 제거한다.
5. 각 단계마다 컴파일, 테스트를 한다.

예제
예제 코드는 Extract Superclass에서 사용했던 코드를 그대로 사용합니다.
0. Extract Superclass로 상위 클래스를 만듭니다.
사용자 삽입 이미지
1. 메소드를 고정적인 부분과 유동적인 부분으로 분해합니다.
– before
사용자 삽입 이미지– Alt + Shift + M 을 사용하여 메소드로 빼낼 수 있습니다.
사용자 삽입 이미지– after
사용자 삽입 이미지
2. Pull Up Method를 사용하여 공통된 부분을 상위 클래스로 옮깁니다.
– 이 때 Alt + Shift + T 로 리팩토링 메뉴를 열고 Pull Up Method를 사용할 수 있습니다.
사용자 삽입 이미지
3. 수퍼클래스에서 추상 메소드로 변경되는 내용을 지닌 메소드를 만들어 줍니다.
– 변경되는 부분의 메소드가 올라가지 않았기 때문에 수퍼클래스에서 에러가 납니다. 에러가 발생한 라인에서 퀵 픽스(Ctrl + 1)를 사용하여 만들 수 있습니다.
사용자 삽입 이미지– abstract protected void doService(Task task);
메소드에 abstract를 붙여주면 클래스도 추상 클래스가 되어야 하며 그럼 추상 클래스의 이름 앞에도 Abstract를 붙여주는 것이 좋겠습니다. Package Explorer에서 클래스르 선택하고 F2 를 클릭하여 클래스 이름을 변경할 수 있습니다.
사용자 삽입 이미지4. 서브클래스를 정리합니다.
– 현재 작업한 서브 클래스는 전부 변경이 되었겠지만 다른쪽 서버클래스는 별다른 작업이 없었기 때문에 공통 부분을 제거하고 추상 메소드를 구현해 줍니다. 이때도 퀵 픽스를 사용하면 편리합니다.

Extract Super Class

비슷한 메소드와 필드를 가진 두 개의 클래스가 있다면, 슈퍼클래스를 만들어서 공통된 메소드와 필드를 수퍼클래스로 옮겨라.

동기
중복을 제거 할 수 있습니다.

절차
1. 빈 수퍼 클래스를 만들고 원래 클래스들이 이것을 상속하도록 한다.
2. Pull Up Filed, Pull Up Method, Pull Up Contruct Body들을 사용하여 공통 요소들을 옮긴다.
3. 옮길 때 마다 테스트를 한다.
4. 공통된 부분이 있다면 Extract Method를 사용한 뒤에 Pull Up Method를 사용할 수 있다.

예제
리팩터링 대상이 된 두 개의 클래스들
– AddController.java
[#M_ more.. | less.. |

public class AddController extends SimpleFormController{

 

       private TaskService taskService;

 

       public void setTaskService(TaskService taskService) {

             this.taskService = taskService;

       }

 

       public AddController() {

             setCommandClass(Task.class);

             setCommandName(“task”);

             setFormView(“task/add”);

             setSuccessView(“task/list”);

       }

 

       @Override

       protected ModelAndView onSubmit(Object command) throws Exception {

             Task task = (Task)command;

             task.setEnrolled(new Date());

             taskService.add(task);

             return new ModelAndView(getSuccessView())

                    .addObject(“taskList”, taskService.getTaskList());

       }

}

_M#]- UpdateController.java

[#M_ more.. | less.. |

public class UpdateController extends SimpleFormController {

 

       private TaskService taskService;

 

       public void setTaskService(TaskService taskService) {

             this.taskService = taskService;

       }

 

       public UpdateController() {

             setCommandClass(Task.class);

             setCommandName(“task”);

             setFormView(“task/update”);

             setSuccessView(“task/list”);

       }

 

       @Override

       protected Object formBackingObject(HttpServletRequest request) throws Exception {

             Integer id = Integer.parseInt(request.getParameter(“id”));

             Task task = taskService.get(id);

             return task;

       }

 

       @Override

       protected ModelAndView onSubmit(Object command) throws Exception {

             Task task = (Task)command;

             task.setEnrolled(new Date());

             taskService.update(task);

             return new ModelAndView(getSuccessView())

                    .addObject(“taskList”, taskService.getTaskList());

       }

}

_M#]
리팩터링
1. 상위 클래스를 만들고 각각의 클래스들이 그 클래스를 상속 받도록 한다.
Eclipse를 사용하신다면 리팩터링 대상이 된 클래스에서 Alt + Shift + T를 클릭하여 리팩터링 메뉴를 띄우고 Extract Super Class를 선택할 수 있습니다.
사용자 삽입 이미지
2. 상위 클래스의 이름을 설정하고 상위로 빼낼 멤버들을 선택합니다.
사용자 삽입 이미지
3. Eclipse의 리팩터링 기능을 사용하여 만들어진 클래스를 상속 받도록 수정 되었습니다.
사용자 삽입 이미지
4. 다른 클래스는 수동으로 상속 받고 있는 클래스를 수정해 주거나 상속 하도록 코드를 수정하고 공통 요소를 하나 둘 빼내거나 Pull Up XXX 리팩토링 기능을 사용하여 옮길 수 있습니다.

참조 : Refactoring

Introduce Parameter Object

참조 : http://www.refactoring.com/catalog/introduceParameterObject.html

여러개의 파라미터를 가지는 메소드의 경우 그 일련의 파라미터들을 가지는 새로운 객체 타입을 받도록 변경할 수 있습니다.

사용자 삽입 이미지
Eclipse의 refactoring 기능 중에 저런 리팩터링을 지원하는 것이 없는지 찾아봤지만 못찾았습니다. 있으면 좋으련만…
http://dev.eclipse.org/mhonarc/lists/eclipse-pmc/msg00188.html
http://download.eclipse.org/eclipse/downloads/drops/S-3.3M7-200705031400/eclipse-news-M7.html
위 글들을 보니 Eclipse3.3의 refactoring 기능에 추가해 줄 것 같습니다.

하나의 메소드 안에 뭉탱이로 들어가는 인자들이 있다면 그 인자들끼리 관계가 밀접할 수 있으며 해당 객체에서 따로 빼내는 것이 애플리케이션을 더 유연하게 만들 수 있습니다.

메소드에 넘겨줄 인자가 단순해집니다.

3장 코드 속의 나쁜 냄새 – 긴 메소드

긴 메소드(Long Method)

최적의 상태로 가장 오래 살아 남는 객체 프로그램은 메소드가 짧다. 인디렉션(indirection)의 이점은 모두 짧은 메소드에 의해 제공되는 것이다.

인디렉션이란?

[#M_위키피디아의 정의 펼쳐 보기|닫기|

Indirection

From Wikipedia, the free encyclopedia

Jump to: navigation, search

In computer programming, indirection is the ability to reference something using a name, reference, or container instead of the value itself.

The simplest form of indirection is the act of manipulating a value through its memory address. For example, accessing a variable through the use of a pointer. A stored pointer that exists to provide a reference to an object by double indirection is called an indirection node.

In some older computer architectures, indirect words supported a variety of more-or-less complicated addressing modes.

Object-oriented programming makes use of indirection extensively, a simple example being dynamic dispatch. Higher-level examples of indirection are the design patterns of the proxy and the proxy server. Delegation is another classic example of an indirection pattern.

In strongly-typed interpreted languages with dynamic datatypes,most variable references require a level of indirection: first the typeof the variable is checked for safety, and then the pointer to theactual value is dereferenced and acted on.

_M#]
원문에는 더 많은 내용이 나와있지만 간략히 요약하면 인디렉션이란 실제 값 자체가 아닌 그것을 참조하는 다른 이름을 사용하는 기능을 말한다는 것 같다. 이렇게 함으로써 프록시와 dynamic dispatch도 가능한 듯하다.

책의 82쪽에는 이런말이 있다.

컴퓨터 과학은 인디렉션 계층을 한 단계 더 만들면 모든 문제를 풀 수 있다고 믿는 학문이다.
-Dennis DeBruler

프로그래밍 초창기에는 서브루틴을 호출 할 때 오버헤드가 있어서 짧은 메소드 사용을 꺼려 했다고 한다. 그러나 요즘엔 프로세스 내부 호출(in-process call)로 인런 오버헤드가 상당부분 제거됐다고 한다. 하지만 서브루틴이 무엇인지 보려면 일단 그 부분으로 가보야 하기 때문에 사람들에게는 여전히 오버헤드가 존재한다. 그 오버헤드를 줄이기 위해서는 메소드의 이름을 잘 지어야 한다.

부분의 경우 메소드의 길이를 줄이기 위해서는 Extract Method를 사용한다. 메소드에 파라미터와 임시변수가 많으면 메소드를 추출하기가 어렵다.

  • 임시변수를 줄이기 위해서는 Replace Temp with Query를 사용할 수 있고
  • 긴 파라미터 리스트는 Introduce Parameter Object와 Preserve Whole Object로 짧게 할 수 있다.
  • 그럼에도 여전히 임시변수와 파라미터가 많다면 Replace Method with Method Object를 사용한다.

뽑아낼 코드 덩어리 식별 방법으로는 주석을 찾는 것이 좋다. 그리고 조건문과 루프 또한 추출이 필요하다는 신호를 준다. 조건식을 다루지 위해서는 Decompose Conditional을 사용한다. 루프의 경우는 Extract Method에서 예제로 다루고 있다.

Extract Class

두 개의 클래스가 해야 할 일을 하나의 클래스가 하고 있는 경우,
새로운 클래스를 만들어서 관련 있는 필드와 메소드를 예전 클래에서 새로운 클래스로 옮겨라.

동기

클래스가 너무 많은 메소드와 데이터를 가지고 있어서 쉽게 이해할 수 없는 경우가 있다. 이때 데이터의 부분집합과 메소드의 부분집합이 몰려 다닌다면 새로운 클래스로 분리할 수 있다는 좋은 신호다.

절차

  1. 클래스의 책임을 어떻게 나눌지를 결정하라.
  2. 분리된 책임을 떠맡을 새로운 클래스를 만든다.
    • 책임을 분리한 후 이전 클래스의 책임이 이름과 더 이상 맞지 않는다면, 이전 클래스의 이름을 변경한다.
  3. 이전 클래스에서 새로 만든 클래스에 대한 링크를 만든다.
    • 양방향 링크가 필요할지도 모른다. 그러나 필요해지기 전에는 새로 만든 클래스에서 이전 클래스로 링크를 만들지 마라.
  4. 옮기고자 하는 각각의 필드에 대해 Move Field를 사용한다.
  5. 각각의 필드를 옮길 때마다 컴파일, 테스트를 한다.
  6. Move Method를 사용해서 이전 클래스에서 새로 만든 클래스로 메소드를 옮긴다. 저수준 메소드(호출하기 보다는 호출되는 메소드)부터 시작해서 짐점 고수준의 메소드에 적용한다.
  7. 각각의 메소드를 옮길 때마다 컴파이르 테스트를 한다.
  8. 각 클래스를 검토하고, 인터페이스를 줄인다.
    • 양방향 링크를 가지고 있다면, 단바양 링크로 만들 수 있는지 알아본다.
  9. 새로운 클래스를 공개할지 결정한다. 새로운 클래슬 공개하기로 결정했다면, 참조 객체로 드러낼지 또는 불변성 값 객체(immutable value object)로 드러낼지를 결정한다.

예제

[#M_코드보기|닫기|
public class Person {

   private String name;
   private String officeAreaCode;
   private String officeNumber;

   public String getName() {
       return name;
   }

   public String getTelephoneNumber() {
       return (“(” + officeAreaCode + “) ” + officeNumber);
   }

   public String getOfficeAreaCode() {
       return officeAreaCode;
   }

   public void setOfficeAreaCode(String officeAreaCode) {
       this.officeAreaCode = officeAreaCode;
   }

   public String getOfficeNumber() {
       return officeNumber;
   }

   public void setOfficeNumber(String officeNumber) {
       this.officeNumber = officeNumber;
   }
}_M#]

위 예제 코드를 보시면 officeAreaCode와 officeNumber 데이타가 몰려 다니고 거기에 따른 getter와 setter들이 몰려 다니는 것을 볼 수 있습니다. 이것을 TelephoneNumber라는 class로 따로 뽑아내기 위해 먼저 TelephoneNumber 클래스를 만들고 Move Field를 사용합니다.

이클립스에서 클래스를 만들고 이동하고자 하는 필드를 선택하고 Alt + Shift + v 를 클릭하면 다음과 같은 팝업창이 뜨게 되고 이동시킬 클래슬 선택할 수 있다.


하지만 단순히 데이터만 옮겨 줄뿐 거기에 딸린 getter와 setter를 같이 옮겨 주지 않는 것은 조금 아쉬웠다. 이번에는 옮기고 싶은 것을 통째로 드래그를 해보았다. 그러나 메소드를 옮기는 일이 쉽지가 않은 듯하다.

결국 잘라내기 붙여넣기를 사용하여 아래와 같이 두개의 클래로 분리하였다.

[#M_Person 클래스 보기|닫기|
public class Person {

   private String name;
   private TelephoneNumber officeTelephone;

   public String getName() {
       return name;
   }

   public String getTelephoneNumber() {
       return officeTelephone.getTelephoneNumber();
   }
}
_M#]
[#M_TelephoneNumber 클래스 보기|닫기|
public class TelephoneNumber {

   private String areaCode;
   private String number;

   public String getTelephoneNumber() {
       return (“(” + areaCode + “) ” + number);
   }

   public String getAreaCode() {
       return areaCode;
   }

   public void setAreaCode(String officeAreaCode) {
       this.areaCode = officeAreaCode;
   }

   public String getNumber() {
       return number;
   }

   public void setNumber(String officeNumber) {
       this.number = officeNumber;
   }

}_M#]