본문 바로가기
기타

리팩토링 스터디 #9.5 - 내용 미리보기

by 유세지 2021. 2. 27.

세 달간 진행되었던 리팩토링 스터디도 어느덧 마지막 시간이 되었습니다. 스터디를 진행하며 정리했던 내용들을 다시 보니 감회가 새롭네요. 마지막까지 알차게 마무리 해야겠습니다.

 

타입 코드를 서브클래스로 바꾸기

특정한 특성에 따라 대상을 구분해야하는 경우 타입 코드 필드를 사용합니다. 여기에 한 발 더 나아가 다양한 기능을 구현하기 위해 서브클래스를 적용할 수 있습니다. 서브클래스를 사용할 경우 두 가지 이점이 있습니다. 첫 번째는 조건에 따라 다르게 동작하는 다형성을 제공할 수 있고, 두 번째는 슈퍼클래스와 서브클래스 사이의 관계를 명확히 파악할 수 있게 만들어줍니다.

 

일단 서브클래스를 이용하기로 마음먹었다면, 서브클래스를 원본 클래스 자체에 적용할지, 타입 코드 필드에 적용할지를 선택합니다. 전자의 경우 원본 클래스를 특성을 가진 클래스로 만들어 적용해주어야 하고, 후자의 경우 속성을 가진 클래스를 따로 만들어 원본 클래스에 넣어주는 식으로 적용시켜주면 됩니다.

 

아래의 절차를 따라 진행합니다.

 

1. 타입 코드 필드를 캡슐화합니다.
2. 타입 코드 값에 해당하는 서브클래스를 만듭니다. 게터 메소드를 오버라이드하여 타입 코드의 리터럴을 반환하게 합니다.
3. 매개변수로 받은 타입 코드와 서브클래스를 매핑하는 로직을 만듭니다.
4. 모든 타입 코드 값에 대해 서브클래스 생성과 선택 로직을 추가해줍니다.
5. 타입 코드 필드를 제거합니다.
6. 타입 코드 접근자를 이용하는 메소드에 메소드 내리기와 조건부 로직을 다형성으로 바꾸기를 적용합니다.

 

 

서브클래스 제거하기

타입 코드를 서브클래스로 바꾸기의 반대 리팩토링입니다. 특징에 따라 여러갈래로 나뉘었던 클래스들이 바뀌고, 사라짐에 따라 서브클래스도 더 이상 필요없어지는 경우가 있습니다. 이럴때는 서브클래스 제거하기를 통해 불필요한 낭비를 줄이는 것이 좋습니다. 쓰이지 않는 내용 때문에 프로그래머가 코드를 들여다 봐야한다면 그 자체로 시간과 기력의 낭비입니다. 바로 제거해줍시다.

 

다음과 같은 절차를 통해 서브클래스를 제거해줍니다.

 

1. 서브클래스의 생성자를 팩토리 함수로 바꿉니다.
2. 서브클래스의 타입을 검사하는 코드들을 슈퍼클래스로 옮깁니다. (함수 추출하기, 함수 옮기기 사용)
3. 서브클래스의 타입을 나타내는 필드를 슈퍼클래스에 만듭니다.
4. 서브클래스를 참조하는 메소드가 타입 필드를 이용하도록 수정합니다.
5. 서브클래스를 삭제합니다.

 

 

계층 합치기

처음엔 필요에 의해 클래스를 분리했을지라도, 시간이 지나며 그 차이점이 희미해져 굳이 구분할 필요가 없는 클래스들이 있다면 두 클래스를 다시 합치는 것도 고려해야합니다. 독립적으로 존재할 이유가 없는 클래스는 부모클래스로 합쳐주는 것이 좋습니다.

 

1. 두 클래스 중 제거할 것을 고릅니다. (이후에 부모클래스로 사용할 이름을 남기는 것이 좋습니다.)
2. 필드 올리기, 메소드 올리기 (혹은 내리기) 등을 이용해 모든 요소를 하나의 클래스로 옮깁니다.
3. 제거할 클래스를 참조하던 코드를 합쳐진 클래스를 참조하도록 바꿔줍니다.
4. 빈 클래스를 제거합니다.

 

 

 

서브클래스를 위임으로 바꾸기

위의 기법들의 공통점은 상속을 통해서 비슷하면서도 다양한 객체들을 효율적으로 다루었다는 점입니다. 그러나 이런 상속은 부모 자식관계를 긴밀하게 연결해주는 장점만큼 단점도 명확합니다. 가장 큰 단점은 여러가지 구별점이 있더라도 한 번에 하나의 기준으로만 어떤 부모를 상속받을지 선택해야하고, 혹시라도 부모가 수정되는 상황이 생긴다면 서브클래스들은 큰 영향을 받을 수 밖에 없습니다. 부모와 자식을 다루는 모듈이 다르거나, 여러 사람이 맡아 구현하게 되면 문제가 될 확률이 더욱 커집니다.

 

이때 위임을 이용하면 서브클래스의 이런 단점들을 피해갈 수 있습니다. 상속에 비해 결합도가 낮은 일반적인 관계이므로 서브클래싱에 관련한 문제를 해결하는데 도움이 됩니다.

 

상속보다는 컴포지션을 사용하라는 말이 있을 정도로 상속보다는 위임이 권장되는 방법이지만, 그렇다고 상속을 사용하는데 주저할 필요는 없습니다. 언제든 필요와 상황에 의해 유동적으로 바꿀 수 있기 때문입니다.

 

아래와 같은 과정에 따라 리팩토링을 적용합니다.

 

1. 생성자를 호출하는 곳이 많을 경우 생성자를 팩토리 함수로 바꿉니다.
2. 위임으로 사용할 클래스를 만들어 서브클래스에 특화된 데이터를 모두 받습니다.
3. 슈퍼클래스에 위임을 저장할 필드를 추가합니다.
4. 서브클래스 생성 코드에 위임 인스턴스를 생성하고 초기화하는 로직을 추가합니다. (팩토리 함수에서 수행)
5. 함수 옮기기를 적용해 서브클래스의 메소드를 위임 클래스로 옮깁니다.
6. 서브클래스 외부에서 원래 메소드를 호출하는 코드가 있으면 슈퍼클래스로 옮깁니다. 호출되지 않는 메소드는 제거합니다.
7. 모든 서브클래스의 메소드를 옮길때까지 반복합니다.
8. 서브클래스를 삭제합니다.

 

 

 

슈퍼클래스를 위임으로 바꾸기

상속을 통한 재활용은 유용하지만 때로는 코드의 복잡도를 증가시키는 방향으로 동작하기도 합니다. 예를 들어 자바의 스택 클래스는 리스트를 상속받고 있는데 스택은 리스트의 모든 연산을 사용하지 않는데도 리스트의 모든 연산들이 스택 인터페이스에 노출되어 있습니다. 서브클래스에 어울리지 않는 기능들이 슈퍼클래스를 통해 제공되고 있다면 이번 기법을 통해 위임으로 전환시켜 주는 것이 필요합니다.

 

상속은 슈퍼클래스의 모든 기능을 사용하고, 서브클래스의 인스턴스를 슈퍼클래스의 인스턴스로도 취급할 수 있어야합니다. 즉, 슈퍼클래스가 사용되는 모든 곳에서 서브클래스의 인스턴스를 사용해도 이상없이 동작하는 것을 의미합니다.

 

여기서 주의할 점이 하나 있습니다. 이름만 보면 같지만 해당 속성을 가진 클래스(타입)인지, 객체(인스턴스)인지 구분할 필요가 있는 서로 다른 두 대상을 타입-인스턴스 동형이의어(type-instance homonym)라고 부릅니다. 두 가지를 혼동하지 않도록 주의해야합니다.

 

물론 위임에는 필요한 기능들은 함수로 만들어야 한다는 단점이 존재합니다. 분명 지루한 일이지만 그만큼 단순하고 문제가 생길 가능성이 적은 방법이기도 합니다. 필요하다는 생각이 들면 주저하지 말고 적용해주세요.

 

다음과 같은 과정을 통해 적용합니다.

 

1. 슈퍼클래스 객체를 참조하는 필드를 서브클래스에 만듭니다. 위임 참조를 새로운 슈퍼클래스 인스턴스로 초기화합니다.
2. 슈퍼클래스의 동작 각각에 대응하는 전달 함수를 서브클래스에 만듭니다. 서로 관련된 함수끼리 그룹으로 묶어 진행합니다.
3. 슈퍼클래스의 동작이 전달 함수로 오버라이드 되면 상속 관계를 해제합니다.

 

 

 

 

 

 

이제 리팩토링 포스팅도 다음 스터디를 마지막으로 마무리가 될 예정입니다.

지금까지 긴 글 읽어주셔서 감사합니다.

반응형

댓글