리팩토링 41. 슈퍼클래스 추출하기

    들어가기 전

    이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다.


    리팩토링 41. 슈퍼클래스 추출하기(Extract Superclass)

    • 슈퍼 클래스 추출하기
      • 두 개 이상의 거대한 클래스가 있고, 상속 구조를 만들 수 있다면 슈퍼 클래스를 추출하면서 클래스의 크기를 줄일 수 있음.
    • 방법
      • 두 개의 클래스에서 비슷한 것들이 보인다면 상속을 적용하고, 슈퍼클래스로 'Pull Up Field' / 'Pull Up Method'를 사용한다.
      • 슈퍼 클래스의 대안으로는 '클래스 추출하기'를 적용해 '위임'을 사용할 수도 있다.
    • 접근
      • 먼저 간단히 상속을 적용한다.
      • 만약 상속 구조가 적절하지 않거나 필요하다면 '슈퍼클래스를 위임으로 교체하기'를 적용한다. 

     

    각 클래스에 공통이 되는 부분이 많고, 클래스 자체가 꽤 큰 편이라면 슈퍼 클래스로 만들어서 공통되는 부분을 재사용하는게 유익할 수 있다. 이 리팩토링을 통해서 거대한 클래스를 작게 만들어 줄 수 있기 때문이다. 뿐만 아니라 다른 접근 방식으로는 '클래스 추출하기 + 위임'을 적용해서 코드를 줄일 수도 있다. 

    슈퍼클래스로 추출하는 것이 간단하기 때문에 아래와 같이 접근하는 것이 좋다. 

    1. 상속 구조를 만들어서 코드 줄이기
    2. 적절하지 않다고 판단되면, 슈퍼 클래스를 위임으로 교체하기를 적용하면 됨.

    코드

    이 코드는 슈퍼 클래스 추출하기의 예시 코드다. 

    Deparment / Employee 클래스는 서로 다른 일을 하는 클래스지만 중복된 필드, 비슷한 메서드가 일부 존재하기 때문에 하나의 슈퍼 클래스로 추출해서 중복되는 코드를 제거할 수 있다. (슈퍼 클래스가 적절한지 판단하는 것은 그 이후의 문제이다). 이 예시에서는 Deparment / Employee 클래스를 통해 Party라는 슈퍼 클래스를 추출하고자 한다. 

    // Department, Employee 비슷한 코드가 있다. 따라서 슈퍼클래스로 추출하기를 통해 중복 코드 제거 가능.
    // 1. 월간 비용 + 연간 비용 지불
    // 2. 이름
    public class Department {
    
        private String name;
        private List<Employee> staff;
    
        public String getName() {return name;}
        public List<Employee> getStaff() {return staff;}
        public double totalMonthlyCost() {return this.staff.stream().mapToDouble(e -> e.getMonthlyCost()).sum();}
        public double totalAnnualCost() {return this.totalMonthlyCost() * 12;}
        public int headCount() {return this.staff.size();}
    }
    
    public class Employee {
    
        private Integer id;
        private String name;
        private double monthlyCost;
    
        public double annualCost() {return this.monthlyCost * 12;}
        public Integer getId() {return id;}
        public String getName() {return name;}
        public double getMonthlyCost() {return monthlyCost;}
    }

    다음 순서대로 리팩토링을 진행한다.

    1. Party 클래스를 만든다.
    2. Employee에 있는 필드 / 메서드들 중 다음을 Party 클래스로 이동시킨다. 이름이 비슷하거나 비슷한 행동을 하는 녀석들이기 때문이다.
      • name()
      • annualCost()
      • monthlyCost()
    3. 메서드 이름이 달랐기 때문에 하나로 통일시켜준다. (annualCost, monthlyCost)
    4. 살펴보니 monthlyCost()는 Employee / Department에서 각각 다르게 동작한다. 따라서 monthlyCost()는 추상 메서드로 만들고, 하위 클래스에서 구현하도록 한다.
    5. Party에 추상 메서드가 생겼기 때문에 Party는 추상 클래스가 된다. 
    6. 하위 클래스에서 name, annualCost()는 제거한다. 

    이 방식대로 리팩토링을 진행하면 결과는 다음과 같다. 

    // MonthlyCost는 클래스마다 구하는 방법이 다르기 때문에 추상 메서드로 변경
    // 그리고 하위에서 재정의한다.
    public abstract class Party {
    
        protected String name;
        protected double monthlyCost;
    
        public double annualCost() {return this.monthlyCost * 12;}
        public abstract double getMonthlyCost();
    }
    
    
    public class Employee extends Party {
    
        private Integer id;
        public Integer getId() {return id;}
        public String getName() {return name;}
    
        @Override
        public double getMonthlyCost() {return super.monthlyCost;}
    }
    
    // Department, Employee 비슷한 코드가 있다. 따라서 슈퍼클래스로 추출하기를 통해 중복 코드 제거 가능.
    // 1. 월간 비용 + 연간 비용 지불
    // 2. 이름
    public class Department extends Party{
    
        private List<Employee> staff;
        public List<Employee> getStaff() {return staff;}
        public int headCount() {return this.staff.size();}
    
        @Override
        public double getMonthlyCost() {return this.staff.stream().mapToDouble(e -> e.getMonthlyCost()).sum();}
    }

    댓글

    Designed by JB FACTORY