
■ 절차
■ 예제
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;
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;
}
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;
}
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;
}

■ 절차
■ 예제
class Account...
private AccountType _type;
private double _interestRate;
double interestForAmount_days (double amount, int days) {
return _interestRate * amount * days / 365;
}
class AccountType...
private double _interestRate;
void setInterestRate (double arg) {
_interestRate = arg;
}
double getInterestRate () {
return _interestRate;
}
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) // 작동안함