: 캡슐화가 잘 되어있을수록 모듈 각각은 내부가 다른 모듈에게 노출되지 않는다. 이 때 캡슐화의 단위는 클래스, 모듈, 심지어 함수도 될 수가 있다. 이 장의 리팩터링을 적용하면 많은 경우 캡슐화를 개선할 수 있다.
1. 레코드 캡슐화하기
무엇이 저장된 값이고, 무엇이 계산된 값인지 바깥에서 알 필요가 없고, 데이터를 더 의미있는 단위로 값을 제공해 줄 수 있게 된다.
1 2 3 4 5 6 7 8 9
// before person = { firstName: "foo", lastName = "bar" }; // after class Person { // .. get name() { return this.firstName + " " + this.lastName; } }
2. 컬렉션 캡슐화하기
컬렉션 자체를 getter로 제공하기보다 각 요소에 대한
add~~()
,remove~~()
를 제공해준다. 이렇게 하면 클라이언트가 실수로 컬렉션 (혹은 가변 데이터)를 바꿀 가능성을 줄일 수 있다.그런다고 컬렉션을 제공하지 않도록하는 것은 여러모로 손해일 수 있다. 이러한 방법 중 하나로 읽기 전용을 제공하는 방법이 있다. (e.g) 복제 혹은
stream()
만 제공하는 방식)1 2 3
public Stream<Person> stream() { return this.persons.stream(); }
코드 일관성: 보통 팀 규칙으로 이런걸 정할텐데, 새로운 팀원은 이러한 규칙을 파악하는데 어려움이 있을 수 있으니 일관성 있게 코드를 작성해야한다.
3. 기본형을 객체로 바꾸기
- 데이터를 표현하는 변수를 클래스화한다. 프로그램 몸집이 커질수록 더더욱 유용하게 된다. 저자는 단순 출력 이상의 기능이 필요해지는 순간 클래스화한다고 한다.
4. 임시 변수를 질의 함수로 바꾸기
임시 변수를 함수로 추출해내면 많은 경우 불필요한 의존관계, 코드 중복을 줄일 수 있다.
1 2 3 4 5
// Before const name = this.firstName + " " + this.lastName; // After get name() { this.firstName + " " + this.lastName; }
- 이 때의 질의함수는 한번만 계산하고 그 뒤에는 읽는 역할만 해야한다. 변경이 발생했는데 재사용이 된다면, 어디서 변경이 일어났는지 알아내기 어려울 수 있다.
5. 클래스 추출하기
- 함께 변경되는 데이터 뭉치 끼리 분리하면서 클래스 몸집을 작게 유지한다.
6. 클래스 인라인하기
- 클래스 역할이 줄어들어 더이상 필요 없을때 or 재배분하고 싶을 때 합쳤다가 다시 분리한다.
7. 위임 숨기기
A 객체의 필드가 가리키는 B 객체의 메서드를 호출하려면 클라이언트는 B를 알아야한다. 해당 객체의 인터페이스가 바뀌면 이 것을 사용하는 모든 클라이언트의 코드가 바뀌어야한다.
객체간 결합도 측면: 객체간 결합도가 높다는 뜻. 보통 이러한 경우의 불필요한 결합도는 끊기 쉬운 경우가 많다.
c.f) 롬복의 @Delegate 어노테이션을 사용하면 특정 메서드를 다른 클래스로 위임 할 수 있다
8. 중개자 제거하기
너무 디미터 법칙을 따르다보면, 어느새 클라이언트와 맡닿는 A 서버 객체가 위임의 역할만 수행하고 있을 수 있다. 그렇게 느낄때마다 제거한다.
보통 시스템이 바뀌면 ‘적절함’의 기준도 바뀐다. 그냥 지금 리팩터링이 필요하면 하면 된다.
9. 알고리즘 교체하기
- 복잡하거나 예전 방식의 알고리즘을 개선하기.
- 이 작업에 들어가기 전에, 교체하기 전의 알고리즘을 하나의 메서드로 나누는게 좋다. 거대한 알고리즘일수록 파악하고 교체하기가 어려우니, 간소화 작업은 필수인 것 같다.
Reference)
리팩터링 2판