Implementaion Patterns 정리

켄트벡의 구현패턴 책 정리

클래스

1. 클래스

  • 함께 사용되는 데이터의 로직을 담고 싶을 때 사용

1-1. 단순한 상위클래스 이름

  • 클래스 이름을 작성할 때는 간결성과 표현성 사이의 고민하게 된다.
  • 이런 딜레마에서 벗어나는 방법은 메타포(metphor)를 사용하는 것이다.
  • 중요한 클래스는 한 단어로 된 이름을 사용하는 것이 좋다.

1-2. 한정적 하위클래스 이름

  • 상위클래스와의 유사점과 차이점을 나타내야한다.
  • 간결성 보다는 표현성을 택하는편이 낫다.
  • 하위클래스 자체로 중요한 개념을 나타내는 경우에는 단순한 이름 사용
  • 클래스의 이름은 코드의 내용을 반영해야 한다.

2. 추상 인터페이스

  • 변화에 유연하게 대응하기 위해 인터페이스로 정의
  • 유연성에는 비용이 들고, 어떤 부분에서 유연성이 필요한지 에측하기는 어렵기 때문에 미리 오버 엔지니어링할 필요 없이 실제 필요할 때에 유연성을 부여하는게 좋다.

2-1. 인터페이스

- 자바 인터페이스를 사용하는 것은 "여기가지가 내가 원하는 것이고, 이외의 내용은 상관하지 않는다" 라고 이야기하는 것과 같다.
- 단순한 상위클래스 이름 : File, 구현클래스(ActualFile, FileImpl)
- 구상클래스의 이름이 중요할 때 : IFile

2-2. 추상 클래스

- 추상 인터페이스는 인터페이스 자체를 바꾸는 것이 매우 어렵다. (구현 클래스가 모두 영항을 받음)
- 추상 클래스를 사용하면 인터페이스와 실제 구현의 차이를 나타낼 수 있다.
- 추상 클래스의 단점은 1개의 상위클래스만 지정할 수 있다는 것.

2-3. 버전 인터페이스

- 인터페이스를 바꾸고 싶지만 바꿀 수 없을 때 사용
1
2
3
4
5
6
7
interface Command {
void run();
}

interface ReversibleCommand extends Command {
void undo();
}

3. 값 객체

  • 상태값을 가진 객체는 호출 순서가 때로 중요한 인터페이스의 일부가된다. 작은 변화가 예측하기 어려운 문제점을 가져온다.
  • 값 객체는 immutable(변경 불가능) 해야한다.

4. 특화

  • 연산 간의 유사점과 차이점을 부각시키는 방향으로 코드를 작성하면, 프로그램을 읽고 사용하고 수정하기가 쉬워진다.

4-1. 하위클래스

- 하위클래스를 선언하는 것은 "이 객체는 상위클래스와 같다. 이 부분만 제외하면.."
- 분류를 나타내는 것이 아니라 구현을 공유하는 것
- 상위클래스의 메소드는 작게 유지하는 것이 좋다.
- 변화하는 로직을 나타낼 때는 조건문이나 위임을 사용하라.

4-2. 구현자

- 다형성 메시지는 여라 가지 변형을 수용한다.

4-3. 인스턴스별 행위

- 클래스의 인스턴스는 모두 같은 로직을 공유하지만 연산 도중 로직이 변하게 할 수 있다.
이런 경우 코드의 이해가 어려워지므로 인스턴스 생성 후에는 행동을 변화시키지 않는 편이 좋다.

    4-3-1. 조건문
    - 단순성과 지역성에서 장점이 있지만 광범위하게 사용되는 경우 중복이 생긴다.

    4-3-2. 위임
    - 분기문는 위임으로 변경할 수 있다.

    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void mouseDown() {
switch(getToll()) {
case SELECTING :
//...
break;
case CREATE_RECTANGLE :
//...
break;
default :
break;
}
}

public void mosueDown() {
getTool().mouseDown();
}
4-3-3. 플러그인 선택자 - 한두개의 메소드에서만 인스턴스별 행동이 필요하고 모든 로직이 하나의 클래스에 들어가도 좋은 경우 - 메소드 이름을 필드에 저장하고 리플렉션을 이용해 호출 4-3-4. 익명 내부클래스 - 지역적으로 한 곳에서만 사용할 때 사용 - 가급적 짧아야한다. 4-4. 라이브러리 클래스 - 유틸 클래스, 정적메소드로 구현 - 남용하면 클래스를 사용한 객체지향의 장점을 잃어버림

상태

1. 상태

  • 유사한 상태는 묶어서 관리 : 동일한 연산에 사용되는지? 라이프사이클이 같은지? 판단

2. 접근

  • 프로그래밍 언어는 접근과 계산으로 나눌 수 있다.
  • 접근과 계산을 구별하고 차이점을 효과적으로 전달해야한다.

2-1. 직접 접근

1
2
x = 10;
doorRegister = 1;
- 표현이 명확하다는 장점이 있지만 유연성이 떨어지고, 코드의 의도를 알기 어려움

2-2. 간접 접근

1
2
3
openDoor() {
doorRegister = 1;
}
- 클래스 내부에서는 직접접근, 외부에서는 간접접근을 사용하는게 좋다.

3. 공용 상태

  • 여러 연산에서 같은 데이터를 사용하는 경우 필드에 선언하는 것이 좋다.
  • 범위와 생명주기가 같아야 한다.
1
2
3
4
class Point {
int x;
int y;
}

4. 가변 상태

  • 맵으로 표현
  • 각 필드의 상태에 따라 다른 필드를 필요한 경우에만 사용

5. 외재 상태

  • 프로그램 일부에서만 특정 상태를 필요로 하는 경우 필드에 선언하지 말고 객체를 필요로 하는 부문에서 저장하자

6. 변수

  • 변수의 생명주기는 변수의 범위와 가까울수록 좋다.

7. 파라미터

  • 필드 참조보다 약한 의존성
  • 반복해서 같은 파라미터를 사용한다면 객체 내로 옮기자

8. 수집 파라미터

  • 여러 메소드 호출을 통한 결과를 모으기 위해 결과를 모으는 파라미터를 전달
1
2
3
4
5
6
7
8
9
10
11
12
13
class Node {
List asList() {
List results = new ArrayList();
addTo(results);
return results;
}

void addTo(List elements) {
elements.add(getValue());
for(Node each : getChildren())
each.addTo(elements);
}
}

9. 옵션 파라미터

  • 필수 파라미터를 앞에, 옵션 파라미터는 뒤에 전달한다.
1
2
3
public ServerSocket()
public ServerSocket(int port)
public ServerSocket(int port, int backlog)

10. 파라미터 객체

  • 여러 개의 파라미터가 함께 그룹으로 전달된다면 객체로 변경하는 것을 고려하자

11. 초기화

  • 변수 초기화는 가급적 선언과 함께하는 것이 좋다.
  • 생성 비용이 크다면 게으른 초기화를 사용하자.