Move Method

  • 메소드가 자신이 정의된 클래스보다 다른 클래스의 기능을 더 많이 사용하고 있다면,
    이 메소드를 가장 많이 사용하고 있는 클래스에 비슷한 몸체를 가진 새로운 메소드를 만들어라. 그리고 이전 메소드는 간단한 위임으로 바꾸거나 완전히 제거하라.

■ 절차

  • 소스 클래스에 정의된 소스메소드에 의해 사용되는 모든 부분을 조사한다.
  • 소스 클래스의 서브클래스나 수퍼클래스에서 옮기려고 하는 메소드에 대한 다른 선언이 있는지 확인한다.
  • 타겟 클래스에 메소드를 정의한다.
  • 소스 메소드에서 타겟 메소드로 코드를 복사한다. 그리고 그 메소드가 타겟 클래스에서 동작하도록 적절히 수정한다.
  • 타겟 클래스를 컴파일한다.
  • 소스 클래스에서 적절한 타겟 객체를 참조하는 방법을 결정한다.
  • 소스 메소드를 위임 메소드로 바꾼다.
  • 컴파일, 테스트를 한다.
  • 소스 메소드를 제거할지 위임 메소드로 남겨둘지를 결정한다.
  • 소스 메소드를 제거한다면 소스 메소드를 참조하고 있는 부분을 타겟 메소드를 참조하도록 수정한다.
  • 컴파일, 테스트를 한다.

■ 예제


class Account...
   double overdraftChange(){
      if(_type.isPreminu()){
          double result = 10;
          if(_daysOverdrawn > 7)  result += (_daysOverdrawn - 7) * 0.85;
          return result;
      }
      else return _daysOverdrawn * 1.75;
   }

   double bankCharge(){
      double result = 4.5;
      if(_daysOverdrawn > 0)  result += overdraftCharge();
      return result;
   }
   private AccountType _type;
   private int _daysOverDrawn;

  • 새로운 계좌 타입 몇가지 추가될 예정
  • overdraftCharge 메소드를 AccountType클래스로 옮기기
  • overdraftCharge method가 사용하는 부분을 살핀다. 옮길만한 가치가 있는지 고려
  • \_daysOverdrawn 필드를 account class에 남겨뒀는데, 이는 개개인의 계좌마다 변할 것이기 때문이다

class AccountType...
  double overdraftCharge(int daysOverdrawn) {
  if (isPremium()) {
     double result = 10;
     if (daysOverdrawn > 7) result += (daysOverdrawn - 7) * 0.85;
     return result;
  }
  else return daysOverdrawn * 1.75;
}

  • source class의 일부분을 사용할 때 네 가지 방법이 있다.
    (1) target으로 옮긴다.
    (2) target에서 source를 참조하는 부분을 새로 만들거나 사용한다.
    (3) method에 source object를 parameter로 넘긴다.
    (4) 사용할 것이 변수라면 parameter로 넘긴다.
  • (4)의 변수를 파라미터로 넘긴 경우

class Account...
  double overdraftCharge() {
    return _type.overdraftCharge(_daysOverdrawn);
}

  • 소스클래스에서 새로운 메소드를 호출하도록 재지정

class Account...
  double bankCharge() {
    double result = 4.5;
    if (_daysOverdrawn > 0) result += _type.overdraftCharge(_daysOverdrawn);
    return result;
}

  • (3) 파라미터로 객체를 넘긴경우

class AccountType...
  double overdraftCharge(Account account) {
     if (isPremium()) {
     double result = 10;
     if (account.getDaysOverdrawn() > 7)
        result += (account.getDaysOverdrawn() - 7) * 0.85;
     return result;
  }
  else return account.getDaysOverdrawn() * 1.75;
}

Move Field

  • 필드가 자신이 정의된 클래스보다 다른 클래스에 의해서 더 많이 사용되고 있다면, 타켓 클래스에 새로운 필드를 만들고 기존 필드를 사용하고 있는 모든 부분을 변경하라.

■ 절차

  • field가 public이면, Encapsulation field를 사용한다.
  • 컴파일, 테스트를 한다.
  • 타겟 클래스에 필드와 그 필드에 대한 get/set 메소드를 만든다.
  • 타겟 클래스를 컴파일한다.
  • 소스 클래스에서 타겟 객체를 참조하는 방법을 결정한다.
  • 소스 클래스에 있는 필드를 제거한다.
  • 소스 필드를 참조하고 있는 모든 부분을 타겟 클래스에 있는 적당한 메소드를 참조하도록 바꾼다.
  • 컴파일, 테스트를 한다.

■ 예제


class Account...
   private AccountType _type;
   private double _interestRate;

   double interestForAmount_days (double amount, int days) {
     return _interestRate * amount * days / 365;
   }

  • \_interestRate 필드를 AccountType으로 옮기고 싶다.
  • AccountType 클래스에 필드와 접근자를 만든다.

class AccountType...
  private double _interestRate;

  void setInterestRate (double arg) {
     _interestRate = arg;
  }
  double getInterestRate () {
    return _interestRate;
  }

  • Account 클래스에 있는 메소드가 AccountType 클래스에 있는 메소드를 사용하도록 재지정하고 Account클래스에 있는 \_intersetRate 필드 제거

private double _interestRate;

   double interestForAmount_days (double amount, int days) {
     return _type.getIneterestRate() * amount * days / 365;
   }

■ 예제 : 자체캡슐화(Self-encapsulation)사용

변경 전변경 후
{code}
class Account...
private AccountType _type;
private double _interestRate;

double interestForAmount_days (double amount, int days) {
return getInterestRate() * amount * days / 365;
}

private void setInterestRate (double arg) {
_interestRate = arg;
}

private double getInterestRate() {
return _interestRate;
}

 | 

double interestForAmoutAndDays (double amount, int das) {
return getInterestRate() * amount * days / 365;
}

private void setInterestRate (double arg) {
_type.setInterestRate(arg);
}

private double getInterestRate () {
return _type.getInterestRate();
}

 |

h3. Extract Class

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

!extractClass.gif!

■ 절차
* 클래스의 책임을 어떻게 나눌지를 결정하라
* 분리된 책임을 떠맡을 새로운 클래스를 만든다
* 이전 클래스에서 새로 만든 클래스에 대한 링크를 만든다
* 옮기고자 하는 각각의 필드에 대해 Move Field를 사용한다
* 각각의 필드를 옮길 때마다 컴파일, 테스트를 한다.
* Move Method를 사용해서 이전 클래스에서 새로 만든 클래스로 메소드를 옮긴다.
* 각각의 메소드를 옮길 때마다 컴파일, 테스트를 한다.
* 각 클래스를 컴토하고, 인터페이스를 줄인다.
* 새로운 클래스를 공개할지 결정한다. 새로운 클래스를 공개하기로 결정했다면, 참조객체로 드러낼지 또는 불변성 값 객체(?) 로 드러낼지를 결정한다.

■ 예제

class Person...
public String getName() {
return _name;
}

public String getTelephoneNumber() {
return ( "(" + _officeAreaCode + ")" + _officeNumber);
}

String getOffice AreaCode() {
return _officeAreaCode;
}

void setOfficeAreaCode(String arg) {
_officeAreaCode = arg;
}

String getOfficeNumber() {
return _officeNumber;
}

void setOfficeNumber(String arg) {
_officeNumber = arg;
}

private String _name;
private String _officeAreaCode;
private String _officeNumber;


* TelephoneNumber 클래스 정의


class TelephoneNumber {
}


* Person클래스에서 TelephoneNumber 클래스에 대한 링크를 만든다

class Person
private TelephoneNumber _officeTelephone = new TelephoneNumber();



* 필드중의 하나에 Move Field를 사용

class TelephoneNumber {
String getAreaCode() {
return _areaCode;
}
void setAreaCode(String arg) {
_areaCode = arg;
}
private String _areaCode;
}

class Person...
public String getTelephoneNumber() {
return ( "(" + getOfficeAreaCode() + ")" + _officeNumber);
}
String getOfficeAreaCode() {
return _officeTelephone.getAreaCode();
}
void setOfficeAreaCode(String arg) {
_officeTelephone.setAreaCode(arg);
}



* Move Method를 사용해서 메소드를 Telephone Number클래스로 옮긴다.

class Person...
public String getName() {
return _name;
}

public String getTelephoneNumber() {
return _officeTelephone.getTelephoneNumber();
}
TelephoneNumber getOfficeTelephone() {
return _officeTelephone;
}

private String _name;
private TelephoneNumber _officeTelephone = new TelephoneNumber();

class TelephoneNumber...
public String getTelephoneNumber() {
return ( "(" + _areaCode + ")" + _number);
}
String getAreaCode() {
return _areaCode;
}
void setAreaCode(String arg) {
_areaCode = arg;
}
String getNumber() {
return _number;
}
void setNumber(String arg) {
_number = arg;
}

private String _number;
private String _areaCode;



* 클래스 공개문제
(1) 임의의 object가 TelephoneNumber의 모든 부분을 변경할 수 있게 한다.
(2) Person class를 통하지 않고 TelephoneNumber class를 변경시키는 것을 허용하지 않는다.
(3) TelephoneNumber를 전달하기 전에 복사하는 것이다. 사람들이 마음대로 변경할 수 있다. aliasing문제 발생 가능

h3. Inline Class

* 클래스가 하는 일이 많지 않은 경우에는, 그 클래스에 있는 모든 변수와 메소드를 다른 클래스로 옮기고 그 클래스를 제거하라.

!inlineClass.gif!

■ 절차
* 흡수하는 클래스에 소스 클래스의 public 필드와 메소드를 선언한다.
* 소스 클래스를 참조하고 있는 모든 부분을 흡수하는 클래스를 참조하도록 변경한다.
* 컴파일, 테스트를 한다.
* Move Method와 Move Field를 사용하여, 소스 클래스에 있는 모든 변수와 메소드를 흡수하는 클래스로 옮긴다.
* 짧고 간단한 장례식을 거행한다.

■ 예제

Extract Class 참조


* Person 클래스에 TelephoneNumber클래스에 있는, 눈에 보이는 모든 메소드를 정의한다.
* TelephoneNumber 클래스의 클라이언트를 찾아서 Person클래스의 인터페이스를 사용하도록 바꾼다.
|| 변경 전 || 변경 후 ||
| 

Person martin = new Person();
martin.getOfficeTelephone().setAreaCode("781");

 | 

Person martin = new Person();
martin.setAreaCode("781");

 |

h3. Hide Delegate

* 클라이언트가 객체의 위임 클래스를 직접 호출하고 있는 경우, 서버에 메소드를 만들어서 대리객체(delegate)를 숨겨라

!hideDelegate.gif!

■ 절차
* 대리객체의 각각의 메소드에 대해, 서버에서 간단한 위임 메소드를 만든다.
* 클라이언트가 서버를 호출하도록 바꾼다.
* 각각의 메소드를 알맞게 바꾸고 나서 컴파일, 테스트를 한다.
* 어떤 클라이언트도 더 이상 대리객체에 접근할 필요가 없다면, 서버 클래스에서 대리 객체에 대한 접근자를 제거한다.
* 컴파일, 테스트를 한다.

■ 예제

class Person {
Department _department;

public Department getDepartment() {
return _department;
}

public void setDepartment(Department arg) {
_department = arg;
}
}

class Department {
private String _chargeCode;
private Person _manager;

public Department (Person manager) {
_manager = manager;
}

public Person getManager() {
return _manager;
}
...


* Person클래스에 간단한 위임 메소드를 만들기

public Person getManager() {
return _department.getManager();
}



* 클라이언트가 어떤 사람의 매니저를 알려고 한다면 먼저 그 사람이 속해 있는 부서를 알 필요가 있다.
* Person클래스에 위임 메소드 추가 후 Person클래스의 모든 클라이언트가 새로 만든 메소드를 사용하도록 변경
|| 변경 전 || 변경 후 ||
| 

manager = john.getDepartment().getManager();

 | 

manager = john.getManager();

 |

h3. Remove Middle Man

* 클래스가 간단한 위임을 너무 많이 하고 있는 경우에는, 클라이언트가 대리객체를 직접 호출하도록 하라.

!removeMiddleMan.gif!
* 어느 정도를 숨기는 것이 적절한지 판단하는 것은 어렵다.

■ 절차
* 대리객체에 대한 접근자를 만든다.
* 서버 클래스에 있는 위임 메소드를 사용하는 각각의 클라이언트에 대해 클라이언트가 대리객체의 메소드를 호출하도록 바꾸고 서버 클래스에 있는 메소드를 제거한다.
* 각각의 메소드에 대한 작업을 마칠 때마다 컴파일, 테스트를 한다.

■ 예제

Hide Delegate 참조


* 대리객체에 대한 접근자를 만든다.

class Person {
public Department getDepartment() {
return _department;
}



* Person클래스의 모든 클라이언트가 대리객체를 얻어서 사용하도록 수정
|| 변경 전 || 변경 후 ||
| 

manager = john.getManager();

 | 

manager = john.getDepartment().getManager();

 |

h3. Introduce Foreign Method
사용하고 있는 서버 클래스에 부가적인 메소드가 필요하지만 클래스를 수정할 수 없는 경우에는, 첫 번째 인자로 서버 클래스의 인스턴스를 받는 메소드를 클라이언트에 만들어라.

* 꼭 필요하지만 그 클래스가 제공하지 않는 서비스가 하나 있다.
* 클라이언트 클래스에서 필요한 메소드를 한 번만 사용한다면 Introduce Foreign Method 사용
* 실제로 서버 클래스에 있어야 하는 메소드라는 것을 명확하게 나타내야 한다.
* 서버 클래스의 외래 메소드를 많이 만들어야 하거나 많은 클래스가 동일한 외래 메소드를 필요한 경우 Introduce Local Exension 사용

■ 절차
* 필요한 작업을 하는 메소드를 클라이언트 클래스에 만든다.
* 첫 번째 파라미터로 서버 클래스의 인스턴스를 받도록 한다.
* 메소드에 "외래 메소드, 원래는 서버 클래스에 있어야 한다."와 같은 주석을 달아 놓는다.

■ 예제
* 대금 결제일을 연기해주는 코드

Date newStart = new Date (previousEnd.getYear(), previousEnd.getMonth(), previousEnd.getDate() + 1);



* 대입문의 우변에 있는 코드를 메소드로 뽑아낼 수 있다. 이 메소드는 Date클래스의 외래 메소드이다.

Date newStart = nextDay(previousEnd);

private static Date nextDay(Date arg) {
//외래 메소드, 원래는 Date 클래스에 있어야 한다
return new Date (arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}




h3. Introduce Local Extension
* 사용하고 있는 서버 클래스에 여러 개의 메소드를 추가할 필요가 있지만 서버 클래스를 수정할 수 없는 경우, 필요한 추가 메소드를 포함하는 새로운 클래스를 만들어라. 이 확장 클래스를 원래 클래스의 서브클래스 또는 래퍼(wrapper)클래스로 만들어라.

!introduceLocalExtension.gif!

* 원래 클래스가 할 수 있는 모든 기능을 지원할 뿐만 아니라 이외의 기능도 가지는 의미에서 서브타입이다.
* 서브클래싱은 그 서브클래스의 새로운 객체를 만들도록 한다.
* 다른 객체가 예전 객체에 접근하고 있다면 원래의 데이터를 가진 두 개의 객체를 가진다. 원래 객체가 불변성이라면 문제가 없다. 
* 그러나, 원래 객체가 가변성이라면, 한 객체에서의 변화가 다른 객체를 변경하지 않는 문제가 발생
* 이런 경우 래퍼를 사용하면 local extension을 통해 변경된 사항이 원래 객체에 영향을 미칠 수 있게 되고, 원래 객체를 통해 변경된 사항은 래퍼에 영향을 미치게 된다.

■ 절차
* 원래 클래스의 서브클래스나 래퍼 클래스로 확장 클래스를 만든다.
* 변환 생성자(converting constructor)를 확장 클래스에 추가한다.
* 새로운 기능을 확장 클래스에 추가한다.
* 필요한 곳에서 원래 클래스에 확장 클래스로 대체한다.
* 이 클래스에 대해 정의된 외래 메소드를 모두 확장 클래스로 옮겨라.

* 첫 번째 할 일은서브클래스를 사용할지 래퍼 클래스를 사용할지 결정하는 것. 서브클래스를 만드는 것이 더 명확한 방법이다.

■ 예제:서브클래스를 사용하는 경우
* Data 클래스를 상속하여 새로 클래스를 만든다.

class MfDataSub extends Date



* 원래 클래스와 확장 클래스 사이의 변환 부분을 다룬다. 원래 클래스의 생성자는 간단한 위임으로 반복될 필요가 있다.

public MfDateSub (String dateString) {
super (dateString);
};


* 원래 클래스를 인자로 받는 변환 생성자를 추가한다.

public MfDateSub (Date arg) {
super (agr.getTime());
};


* 새로운 기능을 확장 클래스에 추가

Clien class
private static Date nextDay(Date arg) {
//외래 메소드, 원래는 Date 클래스에 있어야 한다
return new Date (arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}



 - 이렇게 변화된다.

Class MfDate..
Date nextDay() {
return new Date (getYear(), getMonth(), getDate() + 1);
}



■ 예제:래퍼클래스를 사용하는 경우
* 래핑 클래스를 선언한다.

class mfDate {
private Date _original;
}



* 원래 생성자는 간단한 위임으로 구현된다.


public MfDateSub (String dateString) {
_original = new Date(dateString);
};



* 변환하는 생성자는 이제 인스턴스 변수 값을 정한다.

public MfDateSub (Date arg) {
_original = arg;
};



* 원래 클래스의 모든 메소드를 위임하는 지루한 작업이 남는다.


public int getYear() {
return _original.getYear();
}

public boolean equals (MfDateWrap arg) {
return (toDate().equals(arg.toDate()));
}



* 날짜와 관련된 동작을 새로운 클래스로 가져온다.

Clien class
private static Date nextDay(Date arg) {
//외래 메소드, 원래는 Date 클래스에 있어야 한다
return new Date (arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}



 - 이렇게 변화된다.


Class MfDate..
Date nextDay() {
return new Date (getYear(), getMonth(), getDate() + 1);
}



* 래퍼 클래스를 사용할 때 생기는 특별한 문제는 원래 클래스를 인자로 받는 메소드를 다루는 방법이다.(?)
* 원래 클래스를 변경할 수 없기 때문에 한방향으로만 after메소드를 쓸 수 있다.

aWrapper.after(aDate) // 작동가능
aWrapper.after(anotherWrapper) // 작동가능
aDate.after(aWrapper) // 작동안함