Chapter 11. 일반화 처리
일반화는 주로 상속 계층구조나 상속 계층의 위나 아래로, 즉 상위클래스나 하위클래스로 메서드를 옮기는 기법이다.
하나씩 어떤 것이 있는지 살펴보자.
필드 상향
두 하위클래스에 같은 필드가 들어 있을 때
- 필드를 상위클래스로 옮기자.
public class Employee {
static class Salesman extends Employee {
String name;
}
static class Engineer extends Employee {
String name;
}
}
리팩토링 후
public class Employee {
String name;
static class Salesman extends Employee {
}
static class Engineer extends Employee {
}
}
- 중복된 필드가 서로 비슷한 방식으로 사용된다면 그 필드를 일반화하면 된다.
- 일반화란 상위클래스로 옮기는 작업을 말한다.
메서드 상향
기능이 같은 메서드가 여러 하위클래스에 들어 있을 때
- 그 메서드를 상위클래스로 옮기자.
public class Employee {
String name;
static class Salesman extends Employee {
public String getName() {
return name;
}
}
static class Engineer extends Employee {
public String getName() {
return name;
}
}
}
리팩토링 후
public class Employee {
String name;
public String getName() {
return name;
}
static class Salesman extends Employee {
}
static class Engineer extends Employee {
}
}
- 필드 상향과 마찬가지로 중복된 기능을 없애는 일은 중요하다.
생성자 내용 상향
하위클래스마다 거의 비슷한 내용의 생성자가 있을 땐
- 상위클래스에 생성자를 작성하고, 그 생성자를 하위클래스의 메서드에서 호출하자.
public class Manager extends Employee {
public Manager(String name, String id, int grade) {
this.name = name;
this.id = id;
this.grade = grade;
}
}
리팩토링 후
public class Manager extends Employee {
public Manager(String name, String id, int grade) {
super(name, id);
this.grade = grade;
}
}
메서드 하향
상위 클래스에 있는 기능을 일부 하위클래스만 사용할 땐
- 그 기능을 관련된 하위클래스 안으로 옮기자.
public class Customer {
public int getPrice() {
return 0;
}
static class Guest extends Customer {
}
static class Vip extends Customer {
}
}
리팩토링 후
public class Customer {
static class Guest extends Customer {
public int getPrice() {
return 0;
}
}
static class Vip extends Customer {
}
}
- 메서드 상향과 반대
필드 하향
일부 하위클래스만이 사용하는 필드가 있을 땐
- 그 필드를 사용하는 하위클래스로 옮기자.
public class Customer {
public int price;
static class Guest extends Customer {
}
static class Vip extends Customer {
}
}
리팩토링 후
public class Customer {
static class Guest extends Customer {
public int price;
}
static class Vip extends Customer {
}
}
- 필드 상향과 반대
하위클래스 추출
일부 인스턴스에만 사용되는 기능이 든 클래스가 있을 땐
- 그 기능 부분을 전담하는 하위클래스를 작성하자.
public class JobItem {
private int TotalPrice;
private int unitPrice;
private int EmployeeCount;
public int getTotalPrice() {
return TotalPrice;
}
public int getUnitPrice() {
return unitPrice;
}
public int getEmployeeCount() {
return EmployeeCount;
}
}
리팩토링 후
public class JobItem {
private int TotalPrice;
private int unitPrice;
public int getTotalPrice() {
return TotalPrice;
}
public int getUnitPrice() {
return unitPrice;
}
static class LaborItm {
private int EmployeeCount;
public int getEmployeeCount() {
return EmployeeCount;
}
}
}
상위클래스 추출
기능이 비슷한 두 클래스가 있을 땐
- 상위클래스를 작성하고 공통된 기능들을 그 상위클래스로 옮기자.
- 중복코드는 시스템의 주요 악성 요소 중 하나다.
- 중복된 코드의 한 형태는 비슷한 작업을 같은 방식이나 다른 방식으로 수행하는 두 클래스다.
- 해결법은 상속 구조로 만드는 것이다.
인터페이스 추출
클래스 인터페이스의 같은 부분을 여러 클라이언트가 사용하거나, 두 클래스에 인터페이스의 일부분이 공통으로 들어 있을 땐
- 공통 부분을 인터페이스로 뺴내자.
- 일부분만 사용하는 경우와 특정 기능의 여러 클래스를 함께 사용하는 경우엔, 클래스 기능 중 사용되는 부분을 분리해서 시스템을 사용할 때 사용되는 부분을 확실히 알 수 있게 하는 것이 좋다.
- 클래스가 서로 다른 상황에서 서로 다른 역할을 담당할 때 인터페이스를 사용하면 좋다.
- 클래스 밖으로 뺴낸 인터페이스, 즉 그 클래스가 서버에서 하는 작업을 기술해야 할 때도 인터페이스를 사용하면 좋다.
- 나중에 다른 종류의 서버를 허용해야 할 땐 단지 그 인터페이스를 상속구현 하기만 하면 된다.
계층 병합
상위클래스와 하위클래스가 거의 다르지 않을 땐
- 둘을 합치자.
- 하위클래스가 쓸모 없어지면 하나의 클래스로 합쳐야 한다.
템플릿 메서드 형성
하위클래스 안의 두 메서드가 거의 비슷한 단계들을 같은 순서로 수행할 땐
- 그 단계들을 시그니처가 같은 두 개의 메서드로 만들어서 두 원본 메서드를 같게 만든 후, 두 메서드를 상위클래스로 옮기자.
- 두 메서드가 똑같지는 않지만 거의 비슷한 단계를 같은 순서로 수행하는 경우가 제일 흔하다.
- 이럴 땐 그 순서를 상위클래스로 옮기고 재정의를 통해 각 단계가 고유의 작업을 다른 방식으로 수행하게 하면 된다.
- 이런 메서드를 템플릿 메서드라고 한다.
상속을 위임으로 전환
하위클래스가 상위클래스 인터페이스의 일부만 사용할 때나 데이터를 상속받지 않게 해야 할 땐
- 상위클래스에 필드를 작성하고, 모든 메서드가 그 상위클래스에 위임하게 수정한 후 하위클래스를 없애자.
public class MyStack extends Stack<Integer> {
@Override
public Integer push(Integer item) {
return super.push(item);
}
}
리팩토링 후
public class MyStack {
Stack<Integer> S = new Stack<>(); // 필드 생성
public void push(Integer item) {
S.push(item);
}
}
- 상속 대신 위임을 이용하면 위임받은 클래스의 일부만 사용하려는 의도가 더욱 확실해진다.
- 인터페이스의 어느 부분을 사용하고 어느 부분을 무시할지를 개발자가 제어할 수 있다.
- 단지 위임하는 메서드를 추가로 작성하면 된다.
위임을 상속으로 전환
위임을 이용중인데 인터페이스 전반에 간단한 위임으로 도배하게 될 땐
- 위임 클래스를 대리 객체의 하위클래스로 만들자.
- 이 기법은 이전에 살펴보았던 상속을 위임으로 전환 기법을 반대로 하면 된다.
- 대리 객체의 모든 메서드를 사용하게 되고 그런 간단한 위임 메서드를 지나치게 자주 작성하게 될 때는 계층구조로 꽤 손쉽게 바꿀 수 있다.
- 주의점은 위임하려는 클래스의 모든 메서드를 사용하는게 아닐 경우 위임을 상속으로 전환을 적용해선 안된다.
- 왜냐하면 하위클래스는 반드시 상위클래스의 인터페이스를 따라야 하기 때문이다.
느낀 점
이번에도 꽤나 많은 양의 리팩토링 기법에 대해 알아보았다.
위에서 설명하는 리팩토리에 대해서 살펴보니 공통점이 있었다.
- 소프트웨어에서 중복은 최악이다.
- 이 중복을 없애기 위해 다양한 방법으로 리팩토링할 수 있다.
이번 챕터에서는 상속구조에서의 중복된 필드나 메서드를 어떻게 분리하고 합칠 것이며, 하위클래스가 필요한 경우가 어떤 경우인지, 어떨 때 하위클래스를 없애야 하는지 등등 알아보았다.
스프링으로 개발하면서 어떻게 보면 당연하게 작성했던 코드들도 한번 더 생각해보고 작성하는 것이 좋을 것 같다. 그래야 더 개선할 점이 없는지, 잘못 작성한 코드가 없는지, 그냥 막 작성했는지 파악할 수 있기 떄문이다.
References
'공부 기록' 카테고리의 다른 글
[리팩토링 1판] 마지막 챕터 (0) | 2024.04.12 |
---|---|
[리팩토링 1판] Chapter 12. 복합 리팩토링 (0) | 2024.04.11 |
[리팩토링 1판] Chapter 10. 메서드 호출 단순화 (1) | 2024.04.06 |
[리팩토링 1판] Chapter 09. 조건문 간결화 (1) | 2024.04.04 |
[리팩토링 1판] Chapter 08. 데이터 체계화 (0) | 2024.04.03 |
댓글