본문 바로가기
공부 기록

[리팩토링 1판] Chapter 11. 일반화 처리

by 매트(Mat) 2024. 4. 9.

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;
        }
    }
}

상위클래스 추출

기능이 비슷한 두 클래스가 있을 땐

  • 상위클래스를 작성하고 공통된 기능들을 그 상위클래스로 옮기자.

chapter_11

  • 중복코드는 시스템의 주요 악성 요소 중 하나다.
  • 중복된 코드의 한 형태는 비슷한 작업을 같은 방식이나 다른 방식으로 수행하는 두 클래스다.
  • 해결법은 상속 구조로 만드는 것이다.

인터페이스 추출

클래스 인터페이스의 같은 부분을 여러 클라이언트가 사용하거나, 두 클래스에 인터페이스의 일부분이 공통으로 들어 있을 땐

  • 공통 부분을 인터페이스로 뺴내자.

chapter_11-1

  • 일부분만 사용하는 경우와 특정 기능의 여러 클래스를 함께 사용하는 경우엔, 클래스 기능 중 사용되는 부분을 분리해서 시스템을 사용할 때 사용되는 부분을 확실히 알 수 있게 하는 것이 좋다.
  • 클래스가 서로 다른 상황에서 서로 다른 역할을 담당할 때 인터페이스를 사용하면 좋다.
  • 클래스 밖으로 뺴낸 인터페이스, 즉 그 클래스가 서버에서 하는 작업을 기술해야 할 때도 인터페이스를 사용하면 좋다.
  • 나중에 다른 종류의 서버를 허용해야 할 땐 단지 그 인터페이스를 상속구현 하기만 하면 된다.

계층 병합

상위클래스와 하위클래스가 거의 다르지 않을 땐

  • 둘을 합치자.

  • 하위클래스가 쓸모 없어지면 하나의 클래스로 합쳐야 한다.

템플릿 메서드 형성

하위클래스 안의 두 메서드가 거의 비슷한 단계들을 같은 순서로 수행할 땐

  • 그 단계들을 시그니처가 같은 두 개의 메서드로 만들어서 두 원본 메서드를 같게 만든 후, 두 메서드를 상위클래스로 옮기자.

chapter_11-2

  • 두 메서드가 똑같지는 않지만 거의 비슷한 단계를 같은 순서로 수행하는 경우가 제일 흔하다.
  • 이럴 땐 그 순서를 상위클래스로 옮기고 재정의를 통해 각 단계가 고유의 작업을 다른 방식으로 수행하게 하면 된다.
  • 이런 메서드를 템플릿 메서드라고 한다.

상속을 위임으로 전환

하위클래스가 상위클래스 인터페이스의 일부만 사용할 때나 데이터를 상속받지 않게 해야 할 땐

  • 상위클래스에 필드를 작성하고, 모든 메서드가 그 상위클래스에 위임하게 수정한 후 하위클래스를 없애자.

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

댓글