티스토리 뷰
마지막 장이다. 이번 장에서는 객체 지향 프로그래밍에서 가장 유명한 특성인 상속을 다룬다. 이는 아주 유용한 동시에 오용하기 쉽다. 더욱이 상속은 발등에 불이 떨어져서야 잘못 됐음을 알아차리는 경우가 많다.
특정 기능을 상속계층구조의 위나 아래로 옮길 때
- 12.1 메서드 올리기
- 12.2 필드 올리기
- 12.3 생성자 본문 올리기
- 12.4 메서드 내리기
- 12.5 필드 내리기
계층 사이에 클래스를 추가하거나 제거하는
- 12.8 슈퍼클래스 추출하기
- 12.7 서브클래스 제거하기
- 12.9 계층 합치기
필드값에 따라 동작이 달라지는 코드의 경우 필드를 서브클래스로 대체하고 싶을 때
- 12.6 타입 코드를 서브클래스로 바꾸기
상속이 잘못된 곳에서 사용되거나 나중에 환경이 변해 문제가 생길 경우
- 12.10 서브클래스를 위임으로 바꾸기
- 12.11 슈퍼클래스를 위임으로 바꾸기
12.1 메서드 올리기 (Pull Up Method)
배경
- 서브클래스들의 중복 메서드는 슈퍼클래스로 메서드를 올리는 리팩터링이다
효과
- 중복 코드를 제거한다
적용시점
- 서브클래스들에 중복된 코드가 존재하는 경우
- 상황에 따라 함수 매개변수화(11.2)나 필드올리기(12.2) 리팩터링을 먼저 진행해야 한다
before
class Employee {...}
class Salesman extends Employee {
get name() {...}
}
class Engineer extends Employee {
get name() {...}
}
after
class Employee {
get name() {...}
}
class Salesman extends Employee {...}
class Engineer extends Employee {...}
12.2 필드 올리기 (Pull Up Field)
배경
- 서브클래스들의 중복 필드를 슈퍼클래스로 올리는 리팩터링이다
- 서브클래스들이 독립적으로 개발 되었거나 뒤늦게 하나의 계층구조로 리팩터링된 경우라면 기능이 중복되는 경우가 왕왕 있다
- 필드 이름이 같을수도 있지만 다르더라도 비슷하게 사용할 수 있으니 실제 어떻게 사용되는지 분석이 필요하다
효과
- 필드 중복 선언을 제거한다
- 해당 필드를 사용하는 동작을 서브클래스에서 슈퍼클래스로 옮길 수 있다
적용시점
- 완전히 동일하거나 비슷하게 쓰이는 필드들이 서브클래스에 존재하는 경우
before
class Employee {...} // Java
class Salesman extends Employee {
private String name;
}
class Engineer extends Employee {
private String name;
}
after
class Employee {
protected String name;
}
class Salesman extends Employee {...}
class Engineer extends Employee {...}
12.3 생성자 본문 올리기 (Pull Up Constructor Body)
배경
- 서브클래스 생성자의 중복 코드를 올리는 리팩터링이다
- 다만, 생성자의 경우 할수 있는 일과 호출 순서에 제약이 있기 때문에 다른 방식으로 접근해야 한다
- 공통 코드가 먼저 오거나 순서가 상관없는 경우
- 슈퍼클래스 생성자로 옮긴 후 super() 호출하기
- 공통 코드가 나중에 올 때
- 공통 코드를 함수로 추출(6.1)하여 슈퍼클래스로 옮긴 후 호출하기
효과
- 중복 코드를 제거한다
적용시점
- 생성자에 공통 코드가 존재하는 경우
before
class Party {...}
class Employee extends Party {
constructor(name, id, monthlyCost) {
super();
this._id = id;
this._name = name;
this._monthlyCost = monthlyCost;
}
}
after
class Party {
constructor(name){
this._name = name;
}
}
class Employee extends Party {
constructor(name, id, monthlyCost) {
super(name);
this._id = id;
this._monthlyCost = monthlyCost;
}
}
12.4 메서드 내리기 (Push Down Method)
배경
- 소수의 서브클래스와만 관련된 메서드는 슈퍼클래스에서 제거하고 해당 서브클래스로 내리는 리팩터링이다
- 다만, 호출자가 해당 기능을 제공하는 서브클래스를 정확히 알고 있을 때만 적용할 수 있으며 그렇지 못한 경우 다형성(10.4)으로 바꿔야 한다
효과
- 슈퍼클래스를 깔끔하게 하고 기능을 실제 사용될 위치로 옮겨 용도가 명확해진다
적용시점
- 특정 서브클래스 하나(혹은 소수)와만 관련된 메서드가 슈퍼클래스에 있는 경우
before
class Employee {
get quota {...}
}
class Engineer extends Employee {...}
class Salesman extends Employee {...}
after
class Employee {...}
class Engineer extends Employee {...}
class Salesman extends Employee {
get quota {...}
}
12.5 필드 내리기 (Push Down Field)
배경
- 서브클래스에 하나(혹은 소수)에서만 사용하는 필드는 해당 서브클래스(들)로 옮긴다
효과
- 슈퍼클래스를 깔끔하게 하고 필드를 실제 사용할 위치로 옮겨 용도가 명확해진다
적용시점
- 특정 서브클래스에서만 사용하는 필드가 슈퍼클래스에 있는 경우
before
class Employee { // Java
private String quota;
}
class Engineer extends Employee {...}
class Salesman extends Employee {...}
after
class Employee {...}
class Engineer extends Employee {...}
class Salesman extends Employee {
protected String quota;
}
12.6 타입 코드를 서브클래스로 바꾸기 (Replace Type Code with Subclasses)
배경
- 시스템에서는 비슷한 대상들을 특성에 따라 구분할때 타입 코드 필드를 흔히 사용한다
- 타입 코드만으로도 특별히 불편하진 않지만 그 이상의 무언가가 필요할때는 서브클래스가 필요하다
효과
- 서브클래스는 조건에 따라 다르게 동작하도록 해주는 다형성을 제공한다
- 특정 타입에서만 의미 있는 값을 사용하는 필드나 메서드가 특정 서브클래스로 내려가면 데이터의 관계를 더 명확히 드러낸다
적용시점
- 타입 코드에 따른 다양한 처리 및 분기가 필요한 경우
before
function createEmployee(name, type) {
return new Employee(name, type);
}
after
function createEmployee(name, type) {
switch (type) {
case "engineer": return new Engineer(name);
case "salesman": return new Salesman(name);
case "manager": return new Manager (name);
}
12.7 서브클래스 제거하기 (Remove Subclass)
배경
- 소프트웨어의 시스템이 성장함에 따라 서브클래스가 더 이상 필요하게 않게 될 경우 제거하는 리팩터링이다
- 이런 경우 서브클래스의 필드들을 슈퍼클래스의 필드로 대체한다
효과
- 불필요한 상속을 제거하여 의미 없는 코드를 이해하는데 에너지를 낭비하지 않게 한다
적용시점
- 서브클래스를 사용할 필요가 없어진 경우
before
class Person {
get genderCode() {return "X";}
}
class Male extends Person {
get genderCode() {return "M";}
}
class Female extends Person {
get genderCode() {return "F";}
}
after
class Person {
get genderCode() {return this._genderCode;}
}
12.8 슈퍼클래스 추출하기 (Extract Superclass)
배경
- 유사한 두 클래스가 보이면 비슷한 부분을 공통의 슈퍼클래스로 추출하는 리팩터링이다
- 부모-자식 관계가 아니더라도 상속은 프로그램이 성장하면서 슈퍼클래스로 끌어올리고 싶은 공통 요소가 있을때도 수행한다
효과
- 중복을 제거한다
적용시점
- 중복 동작을 상속으로 해결하느냐 위임으로 해결나느냐를 결정 후 진행한다
- 저자는 슈퍼클래스 추출하기 방법이 더 간단할 경우가 많으니 이 리팩터링을 먼저 시도해보길 권한다
before
class Department {
get totalAnnualCost() {...}
get name() {...}
get headCount() {...}
}
class Employee {
get annualCost() {...}
get name() {...}
get id() {...}
}
after
class Party {
get name() {...}
get annualCost() {...}
}
class Department extends Party {
get annualCost() {...}
get headCount() {...}
}
class Employee extends Party {
get annualCost() {...}
get id() {...}
}
12.9 계층 합치기 (Collapse Hierarchy)
배경
- 클래스 계층구조를 리팩터링하다 보면 기능들을 위로 올리거나 아래로 내리는 일은 다반사로 벌어진다
- 이 때 어떤 클래스와 그 부모가 너무 비슷해져서 더는 독립적으로 존재할 필요가 없는 경우 하나로 합쳐야 한다
효과
- 불필요한 클래스를 제거한다
적용시점
- 어떤 클래스와 부모가 너무 비슷해져서 독립적으로 존재할 필요가 없는 경우
- 두 클래스 중 제거할 것을 고른 후 시작한다
before
class Employee {...}
class Salesman extends Employee {...}
after
class Employee {...}
12.10 서브클래스를 위임으로 바꾸기 (Replace Subclass with Delegate)
배경
- 상속 받는 서브클래스 대신 위임(delegate)으로 만들어 제공하는 리팩터링이다
- 속한 갈래에 따라 동작이 달라지는 객체들은 상속으로 표현하는게 자연스러우며 구현하기가 쉽다
- 하지만 상속은 한 번만 쓸 수 있는 카드라는 단점과 클래스들의 관계를 긴밀하게 결합한다는 단점이 있다
- 위임은 결합도가 약하여 위 두 문제를 모두 해결해준다
효과
- 상속으로 해결하기 어려운 부분을 해결해준다
적용시점
- 처음에는 상속으로 접근한 다음, 문제가 생기기 시작하면 위임으로 갈아탄다
- "상속보다는 컴포지션(위임)을 사용하라" 는 유명한 원칙이 있는데, 이 원칙은 상속을 쓰지 말라는게 아니라 과용하는데 따른 반작용으로 나온 것이다
before
class Order {
get daysToShip() {
return this._warehouse.daysToShip;
}
}
class PriorityOrder extends Order {
get daysToShip() {
return this._priorityPlan.daysToShip;
}
}
after
class Order {
get daysToShip() {
return (this._priorityDelegate)
? this._priorityDelegate.daysToShip
: this._warehouse.daysToShip;
}
}
class PriorityOrderDelegate {
get daysToShip() {
return this._priorityPlan.daysToShip
}
}
12.11 슈퍼클래스를 위임으로 바꾸기 (Replace Superclass with Delegate)
배경
- 슈퍼클래스 대신 위임을 사용하는 리팩터링이다
- 슈퍼클래스의 기능들이 서브클래스에는 어울리지 않는다면 그 기능들을 상속을 통해 이용하면 안된다는 신호다
- 이런 경우 상속을 버리고 위임으로 갈아타 객체를 분리하여 혼란과 오류를 피해야 한다
효과
- 위임을 이용하면 기능 일부만 빌려올 뿐인, 서로 별개인 개념이 명확해진다
적용시점
- 기능을 온전히 사용할 수 없는 어울리지 않는 클래스를 상속 받은 경우
before
class List {...}
class Stack extends List {...}
after
class Stack {
constructor() {
this._storage = new List();
}
}
class List {...}
참고
http://www.yes24.com/Product/Goods/89649360
https://refactoring.com/catalog
'소프트웨어공학, CS > 리팩터링 2판' 카테고리의 다른 글
[리팩터링 2판] 1장 리팩터링: 첫번째 예시 (0) | 2022.03.20 |
---|---|
[리팩터링 2판] 3장 코드에서 나는 악취 (0) | 2021.09.04 |
[리팩터링 2판] 11장 API 리팩터링 (0) | 2021.05.16 |
[리팩터링 2판] 10장 조건부 로직 간소화 (0) | 2021.04.18 |
[리팩터링 2판] 9장 데이터 조직화 (0) | 2021.04.04 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- https
- Debug It! 실용주의 디버깅
- 안심 첫 문장
- aws fargate
- 그림으로 배우는 HTTP & Network
- Debug
- 일잘러
- 코드스멜
- 매개변수화
- 조건부 로직
- 그림으로 공부하는 IT 인프라 구조
- 마틴파울러
- 박소연
- 일 잘하는 사람은 단순하게 말합니다
- Refactoring
- 위임
- 제어플래그
- HTTP
- amazon aurora
- 디버깅
- 질의함수
- AWS
- 변경함수
- 지시의 언어
- 코드악취
- 리팩터링이란
- 리팩토링
- Debugging
- SSL
- amazon vpc
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
글 보관함