Home 리팩토링(5)_객체 사이의 기능 이동
Post
Cancel

리팩토링(5)_객체 사이의 기능 이동

메서드 이동

하나의 클래스에서 기능이 너무 많거나 클래스가 다른 클래스와 과하게 연동이 되어 의존성이 지나칠 때는 메서드를 옮기는 것이 좋다. 메서드를 옮기면 클래스가 간결해지고 여러 기능을 더 명확하게 할 수 있다.

방법

  1. 원본 클래스에 정의되어 있는 메서드에 사용되는 모든 기능을 확인한다. 기능을 확인하며 그 기능들도 전부 옮겨야 하는지 판단한다.
    • 옮길 메서드에서만 사용이 되는 기능도 함께 옮길 수 있어야 한다. 그 기능이 다른 메서드에도 사용이 된다면 함께 옮기는 것을 고려해야 한다.
  2. 원본 클래스의 하위 클래스와 상위 클래스에서 메서드에 대한 다른 선언이 있는지 확인한다.
    • 다른 선언이 있다면 대상 클래스에도 재정의를 넣을 수 있을 때만 옮길 수 있을지도 모른다.
  3. 이동할 메서드를 옮길 클래스 안에 선언한다.
  4. 원본 메서드의 코드를 대상 메서드에 복사한 후, 대상 클래스 안에서 정상 동작하도록 대상 메서드를 수정한다.
    • 대상 매서드가 원본 매서드를 사용한다면 대상 메서드 안에서 원본 객체를 참조할 방법을 정해야 한다. 대상 클래스에 원본 객체를 참조하는 기능이 없다면 원본 객체 참조를 대상 매서드에 매개 변수로 전달한다.
    • 대상 매서드에 예외처리 코드가 들어 있다면 예외를 논리적으로 어느 클래스가 처리할지 정한다.
  5. 대상 클래스를 컴파일 한다.
  6. 원본 객체에서 대상 객체를 참조할 방법을 정한다.
    • 대상 클래스를 참조하는 속성이나 메서드가 없고 대상 클래스를 참조하는 메서드를 쉽게 작성할 수 없다면 원본 클래스 안에 대상 클래스를 저장할 수 있는 새 속성을 선언해야 한다.
  7. 원본 메서드를 위임 메서드로 전환한다.
  8. 컴파일과 테스트를 진행한다.
  9. 원본 메서드를 삭제하거나 위임 메서드로 사용하도록 유지한다.
  10. 컴파일 후 테스트를 진행한다.

필드 이동

클래스 안에 있는 어떠한 필드가 자신이 속한 클래스보다 다른 클래스에 있는 메서드를 더 많이 참조해서 정보를 이용한다면 그 필드를 다른 클래스로 옮기는 것이 필요할 수 있다. 인터페이스에 따라서 메서드를 옮기는 방법을 사용할 수도 있지만 메서드는 현재의 위치가 적절하다고 생각이 될 경우, 필드를 옮긴다.

방법

  1. 필드가 public이라면 필드 캡슐화를 실시한다.
  2. 테스트를 실시한다.
  3. 옮길 대상 클래스 안에 읽기 / 쓰기 메서드와 함께 필드를 작성한다.
  4. 원본이 되는 클래스에서 대상 클래스를 참조할 수 있는 방법을 선정한다.
  5. 원본 클래스에서 필드를 삭제한다.
  6. 원본 필드를 참조하는 모든 부분을 대상 클래스에 있는 적절한 메스드를 참조하여 수정하도록 한다.
  7. 테스트를 진행한다.

필드 캡슐화

필드 캡슐화 전

1
2
3
class Address {
    public String city;
}

필드 캡슐화 후

1
2
3
4
5
6
7
8
9
10
11
class Address {
	private String city;
	
	public String getCity() {
		return this.city;
	}
	
	public void setCity(String city) {
		this.city = city;
	}
}

필드 자체 캡슐화

많은 메서드가 특정 필드를 사용한다면 필드 자체 캡슐화를 진행하는 것이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Account {
    private AccountType type;
    private double interestRate;
    
    double interestForAmoutDays (double amount, int days) {
        return getInterestRate() * amount * days / 365;
    }
    
    private void setInterestRate (double interestRate) {
        this.interestRate = interestRate;
    }
    
    private double getInterestRate () {
        return interestRate;
    }
}

클래스 추출

두 개의 클래스가 처리해야 하는 기능이 하나의 클래스에 들어 있을 때, 새로운 클래스를 만들고 기존 클래스와 관련된 필드와 캐서드를 새 클래스로 옮겨야 한다.

방법

  1. 클래스의 기능 분리 방법을 정한다.
  2. 분리한 기능을 넣을 새로운 클래스를 작성한다.
    • 원본 클래스의 기능이 이름과 어울리지 않게 바뀌었다면 원본 클래스의 이름을 변경할 필요가 있다.
  3. 원본 클래스에서 새 클래스로의 링크를 만든다.
  4. 옮길 필드마다 필드 이동을 적용한다.
  5. 필드를 하나씩 옮길 때마다 컴파일과 테스트를 실시한다.
  6. 메서드 이동을 통해서 원본 클래스의 메서드를 하나씩 새로운 클래스로 옮긴다. 하급 메서드(호출되는 메서드) 부터 상급 메서드(호출하는 메서드)에 적용한다.
  7. 메서드 이동을 실시할 때마다 테스트를 진행한다.
  8. 각 클래스를 다시 검사해서 인터페이스를 줄인다.
    • 양방향 링크가 있다면 단방향으로 바꿀 수 있는지 결정한다.
  9. 여러 곳에서 클래스에 접근할 수 있게 할지 결정한다.
    • 여러 곳에서 접근할 수 있게 할 경우, 새로운 클래스를 참조 객체나 변경불가 객체로서 공개할지 여부를 결정한다.

주의 사항

클래스 추출을 통해 새로 생성된 클래스를 공개하는 방식을 사용할 때, 왜곡의 위험을 고려해야 한다. 변경한 클래스의 주체가 직접 호출한 클라이언트가 아니라 클라이언트의 클라이언트일 수도 있기 때문이다. 이러한 경우, 다음과 같은 3가지 중 하나를 선택해야 한다.

  • 모든 객체가 새로 생성된 클래스의 어느 부분이든 변경할 수 있음을 받아들인다. 이러면 새로운 클래스가 참조 객체가 되므로 값을 참조로 전환 방법을 실시해야 한다.
  • 어떤 주체든 분리를 시킨 원본 클래스를 거치지 않고는 새로 생성된 클래스의 값을 변경하지 못하게 한다.
  • 새로 생성된 클래스를 외부로 전달하기 전에 복사한 후 변경불가로 만든다.

참고자료

  • 리팩토링_코드 품질을 개선하는 객체지향 사고법 _마틴파울러 저