본문 바로가기

[켄트 벡의 구현 패턴] 4장 동기유발, 5장 클래스, 6장 상태

4. 동기유발

소프트웨어 전체 비용 = 개발 비용 + 유지비용

유지 비용 = 이해 비용 + 수정 비용 + 테스트 비용 + 설치비용

초기 코드 개발에 대한 투자를 크게 확대 하는 것은 비효율적

1. 화폐 가치는 시일이 지나면 떨어지므로, 비용 발생 시점을 뒤로 미루는 것이 좋음.

2. 불확실성 측면에서 볼 때, 이후 이득을 가져올지도 모르는 구현  < 당장 이득을 얻을 수 있는 구현

-> 구현 패턴은 미래 코드 확장이나 수정이 쉬운 깔끔한 코드를 추구하면서도, 당장 얻을 수 있는 이득에 초점.

 

저자가 사용하는 비용 절감 : 모든 프로그래머가 커뮤니케이션하기 쉬운 코드를 짬으로써, 유지 비용을 줄이는 것.

이 경우 깔끔한 코드를 통해 코드의 결함이 줄어들고 코드 공유가 쉬워져서 좀더 원활한 개발이 가능.

 

5. 클래스

클래스 : "이 데이터들은 함께 사용되는데, 그에 관련된 로직이 이것이다."라는 이야기를 하고 싶을 때 클래스를 사용한다.

단순한 상위 클래스 이름 : 클래스 계층의 최상위에 위치하는 클래스 이름은 단순하게 짓는다. 

한정적 하위 클래스 이름 : 상위클래스와의 유사점과 차이점을 분명히 드러내는 이름을 사용한다. 

추상 인터페이스 : 인터페이스와 구현을 분리한다.

- 구현이 아니라 인터페이스에 맞춰 코딩하라

- 설계상의 결정을 필요 이상으로 노출하지 말라.

- 자바에서 제공하는 추상인터페이스는 1. 상위 클래스 2. 자바 인터페이스 

인터페이스 : 자주 변하지 않는 추상 인터페이스에는 자바 인터페이스를 사용한다. 

- 자바 인터페이스를 사용하는 것은 "여기까지가 내가 원하는 것이고, 이외의 내용은 상관하지 않는다."라고 이야기 하는것.

추상클래스 : 자주 바뀔 것 같은 추상 인터페이스에는 추상 클래스를 사용한다. 

- 자바 클래스에서 abstract 키워드를 씀으로써, 해당 클래스를 사용하기 위해서는 반드시 구현이 필요하다는 사실을 코드 독자에게 전달할 수 있음. 클래스 계층에서 최상위에 위치한 클래스를 인스턴스화해서 사용할 가능성이 조금이라도 있다면 그렇게 하라. 추상화를 진행하다 보면, 쓸데없는 추상 클래스를 너무 많이 만들게 되는 경우가 생긴다. 최상위 클래스를 인스턴스화 가능한 클래스로 만들면 이런 쓸데 없는 추상 클래스 계층을 제거할 수 있다. (p. 60) >> 잘 이해안됨.

  추상클래스 인터페이스
인터페이스 수정의 쉬운가 쉬움 어려움
단일 클래스가 여러 인터페이스를 지원하는가 1개의 상위 클래스 지정 여러개

버전 인터페이스 : 하위인터페이스를 사용해 기존 인터페이스를 안전하게 확장한다. 

- 인터페이스를 변경하고 싶을 때, 새로운 인터페이스를 선언하여 기존 인터페이스를 확장(상속)한 후 새로운 연산 추가 가능.

- 새로운 연산을 사용하는 경우, 반드시 객체의 타입을 확인 후 새로운 타입으로 다운 캐스트 해서 사용.

- instanceof를 사용하는 경우, 코드가 특정 클래스에 제한되므로 유연성이 떨어지지만, 인터페이스를 개선할 수 있으므로 instanceof의 사용이 정당화 됨.

interface Command {
	void run();
}

interface ReversibleCommand extends Command {
	void undo();
}

Command recent = ...;
if (recent instanceof ReversibleCommand) {
	ReversibleCommand downCasted = (ReversibleCommand) recent;
    downCasted.undo();
}

값 객체 : 산술 값처럼 동작하는 객체를 사용한다. 

특화 : 관련된 연산 사이의 유사점 및 차이점을 분명하게 나타낸다. 

하위클래스 : 1차원적 변화는 하위 클래스를 사용해서 표현한다. 

- 하위클래스를 선언하는 것은 "이 객체는 상위클래스와 같아. 이 부분만 제외하면.."이라는 뜻

구현자 Implememtor : 연산 내용이 바뀌었다면 기존 메소드를 오버라이드해서 사용한다. 

내부 클래스 : 클래스 내부에서 유용하게 사용할 수 있는 코드를 모아 전용 클래스로 사용한다. 

인스턴스별 행동 instance-specific behavior : 인스턴스에 따라 로직에 변화를 준다. 

조건문 : 명시적 조건에 따라 로직에 변화를 준다.

위임 delegation : 여러 종류의 객체 중 하나에 위임해서 로직에 변화를 준다. 

플러스인 선택자 pluggable selector : 리플렉션을 이용한 메소드 호출로 로직에 변화를 준다. 

익명 내부 클래스 : 필요한 메소드에서 한두 개의 메소드만 오버라이드하는 객체를 만들어서 사용한다. 

라이브러리 클래스  :마땅히 들어갈 곳이 없는 기능들을 묶어서 정적 메소드로 표현한다. 

 

6. 상태

- 객체는 외부에 드러아는 행위와 행위를 지원하기 위한 상태를 묶어주는 편리한 단위.

상태 : 시간에 따라 변화하는 값을 사용하여 연산한다. 

- 함수형 프로그래밍 언어에는 변화하는 상태의 개념이 없다. 

- 객체 지향 언어는 상태를 다루는데 적합한 전략이다. 

- 효과적인 상태를 관리하기 위한 키포인트는 유사한 상태를 묶어서 관리하고 각 상태를 변도로 관리하는 것이다. 

1) 두 개의 상태가 동일한 연산 안에서 사용되고

2) 동일한 시점에서 생성되고 소멸되는지 보면 됨.

접근 : 상태에 대한 접근을 제한해서 유연성을 조절한다. 

직접 접근 : 객체 내의 상태를 직접 접근한다. 

- 장점 : 표현의 명확성

- 단점 : 유연성이 없음. 프로그램을 수정할 때 변수를 접근하는 모든 부분을 수정해야 함. 프로그래밍할 때 프로그래머가 사고하는 수준보다 낮은 수준의 세부 구현을 사용한다는 것.

간접 접근 : 좀더 나은 유연성을 위해 메소드를 통해 상태에 접근한다. 

- 메소드 호출을 통해 상태 변화를 숨길 수 있음. 

- 메소드를 사용하는 입장에서는 저장되는 값을 직접 알 필요 없음. 따라서 기존 코드를 바꾸지 않고도 저장소 결정과 같은 문제를 나중에 변경할 수 있음.

- 클래스 내부(내부 클래스 포함)에는 직접 접근을 허락하지만, 클래스 외부에서는 간접 접근을 사용하는 것을 권장.

공용 상태 : 클래스의 모든 인스턴스에 적용되는 상태는 필드에 저장한다. 

- 여러 연산에서 같은 데이터 요소를 사용하는 경우, 클래스에 필드를 선언해서 사용하는 것이 좋다. 

- 각 객체의 공용 상태는 모두 범위와 생명기간이 같아야 함.

가변 상태 : 같은 클래스의 인스턴스마다 다른 상태를 유지해야 할 경우 상태를 맵에 저장한다. 

외재 상태 : 객체와 연동된 특수 상태는 상태의 사용자가 소유하는 맵에 저장한다. 

변수 : 변수 상태 접근에 필요한 네임스페이스를 제공한다. 

지역 변수 : 지역 변수는 단일 범위 내에서만 유효한 상태를 저장한다. 

필드 : 필드는 객체가 생성될 때부터 소멸될 때까지 상태를 저장한다. -> 각각의 예시가 있음 좋겠다. 

파라미터 : 파라미터는 메소드가 활성화된 동안 상태를 전달한다. 

수집 파라미터 : 여러 개의 메소드를 통해 복잡한 결과를 얻기 위해 파라미터를 전달한다. 

옵션 파라미터 

public ServerSocket()
public ServerSocket(int port)
public ServerSocket(int port, int backlog)

가변 인자 : 타양한 개수의 인자를 취할 수 있는 메커니즘을 제공, 가변 인자는 항상 마지막 파라미터여야 함.

파라미터 객체 : 자주 사용하는 긴 파라미터 목록은 객체로 만들어서 통합한다.

- 사용 코드가 짧아지고, 의도를 명확히 전달할 수 있음.

상수 : 변하지 않는 상태는 상수로 저장한다. 

역할 제시형 작명 : 변수 이름은 연산에서의 역할을 반영하여 짓는다. 

선언형 타입 : 변수에 대한 일반적 타입을 선언한다. 

초기화 : 변수 초기화는 가급적 선언적으로 한다. 

열성적 초기화 : 인스턴스가 생성될 때 필드를 초기화한다. 

게으른 초기화 : 초기화 비용이 높은 객체의 경우, 객체가 실제 사용되기 직전에 초기화 한다.