객체지향이란 객체를 지향하는 것이다. 따라서 객체지향 패러다임의 중심에는 객체가 위치한다. 그러나 각 객체를 따로 떼어두고 이야기하는 것은 무의미함.
객체지향에서 가장 중요한 것은 애플리케이션의 기능을 구현하기 위해 협력에 참여하는 객체들 사이의 상호작용이다. 객체들은 협력에 참여하기 위해 역할을 부여받고 역할에 적절한 책임을 수행한다.
자율적인 객체
- 클래스의 내부와 외부를 구분해야 하는 이유는 무엇인가 ? 그 이유는 경계의 명확성이 객체의 자율성을 보장하기 때문이다. 그리고 더 중요한 이유로 프로그래머에게 구현의 자유를 제공하기 때문이다.
- 객체 내부에 대한 접근을 통제하는 이유는 객체를 자율적인 존재로 만들기 위해서임. 객체지향의 핵심은 스스로 상태를 관리하고, 판단하고, 행동하는 자율적인 객체들의 공동체를 구성하는 것
- 캡슐화와 접근 제어는 객체를 두 부분으로 나눔
- 외부에서 접근 가능한
퍼블릭 인터페이스
- 외부에서 접근 불가능하고 오직 내부에서만 접근 가능한
구현
퍼블릭 인터페이스
에는 public으로 지정된 메서드만 포함되고, 그 밖의 private 메서드나 protected 메서드, 속성은구현
에 포함됨
협력하는 객체들의 공동체
public class Money { public static final Money ZERO = Money.wons(0); private final BigDecimal amount; public static Money wons(long amount) { return new Money(BigDecimal.valueOf(amount)); } ... }
- 금액과 관련된 값은 Long 타입을 사용해 나타낼 수도 있지만 이는
Money
타입처럼 저장하는 값이 금액과 관련되어 있다는 의미를 전달할 수가 없음. 또한 금액과 관련된 로직이 서로 다른 곳에 중복되어 구현되는 것을 막을 수가 없음
- 객체지향의 장점은 객체를 이용해 도메인의 의미를 풍부하게 표현할 수 있다는 것임. 따라서 의미를 좀 더 명시적이고 분명하게 표현할 수 있다면 객체를 사용해서 해당 개념을 구현하라.
- 그 개념이 비록 하나의 인스턴스 변수만 포함하더라도 개념을 명시적으로 표현하는 것은 전체적인 설계의 명확성과 유연성을 높이는 첫걸음이다.

협력에 관한 짧은 이야기
- 객체가 다른 객체와 상호작용할 수 있는 유일한 방법은
메시지를 전송
하는 것 뿐이다.
- 다른 객체에게 요청이 도착할 때 해당 객체가
메시지를 수신
했다고 이야기 함
- 메시지를 수신한 객체는 스스로의 결정에 따라 자율적으로 메시지를 처리할 방법을 결정하고 이처럼 수신된 메시지를 처리하기 위한 자신만의 방법을
메서드
라고 부름
메시지
와메서드
의 구분에서부터다형성
의 개념이 출발함
상속과 다형성
컴파일 시간 의존성과 실행 시간 의존성
- 코드의 의존성과 실행 시점의 의존성이 서로 다를 수 있음. 다시 말해 클래스 사이의 의존성과 객체 사이의 의존성은 동일하지 않을 수 있다. 그리고 유연하고, 쉽게 재 사용할 수 있으며 확장 가능한 객체지향 설계가 가지는 특징은 코드의 의존성과 실행 시점의 의존성이 다르다는 것임
- 간과해서는 안 되는 사실은 코드의 의존성과 실행시점의 의존성이 다르면 다를수록 코드를 이해하기 어려워짐. 코드를 이해하기 위해서는 코드 뿐 아니라 객체를 생성하고 연결하는 부분을 찾아야 하기 때문. 이것이 설계가 트레이드오프의 산물이라는 사실을 잘 보여줌
- 훌륭한 객체지향 설계자로 성장하기 위해서는 항상 유연성과 가독성 사이에서 고민해야 한다. 설계가 유연해 질수록 코드를 이해하고 디버깅하기는 점점 더 어려워진다는 사실을 기억하라
다형성
- 다시 한번 강조하면, 메시지와 메서드는 다른 개념이다. Movie는 DiscountPolicy의 인스턴스에게 calculateDiscountAmount
메시지
를 전송 - 실행되는 메서드는 Movie와 상호작용하기 위해 연결된 객체의 클래스가 무엇인가에 따라 달라짐
- Movie와 협력하는 객체가 AmountDiscountPolicy 의 인스턴스라면 AmountDiscountPolicy 에서 오버라이딩한
메서드
가 실행되는 것임
- 다형성이란 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력을 의미
- 다형성을 구현하는 방법은 매우 다양하지만 메시지에 응답하기 위해 실행될 메서드를 컴파일 시점이 아닌 실행 시점에 결정한다는 것이 공통점. 다시 말해 메시지와 메서드를 실행 시점에 바인딩 한다는 것 ⇒
지연 바인딩
or동적 바인딩
- 그에 반해 전통적인 함수 호출처럼 컴파일 시점에 실행될 함수나 프로시저를 결정하는 것을
초기 바인딩
또는정적 바인딩
이라고 부름
- 클래스를 상속받는 것 만이 다형성을 구현할 수 있는 유일한 방법은 아님.
구현 상속과 인터페이스 상속
상속을 구현 상속과 인터페이스 상속으로 분류할 수 있다.
흔히 구현 상속을
서브 클래싱
이라 부르고, 인터페이스 상속을 서브 타이핑
이라고 부른다.
순수하게 코드를 재사용하기 위한 목적으로 상속을 사용하는 것을 구현상속
이라 하고 , 다형적인 협력을 위해 부모 클래스와 자식 클래스가 인터페이스를 공유할 수 있도록 상속을 이용하는 것을 인터페이스 상속
이라고 부른다.
상속은 구현 상속이 아니라 인터페이스 상속을 위해 사용해야 한다. 대부분의 사람들은 코드 재사용을 상속의 주된 목적이라고 생각하지만 이것은 오해다. 인터페이스를 재사용할 목적이 아니라 구현을 재사용할 목적으로 상속을 사용하면 변경에 취약한 코드를 낳게 될 확률이 높다.
이펙티브 자바 아이템18 : (구현)상속보다는 컴포지션을 사용하라
⇒ 구현 상속보다는 컴포지션으로 하기.
⇒ 동일한 타입을 갖고 싶다면 인터페이스 상속을 하기추상화와 유연성
유연한 설계
- 위의 예시에서 스타워즈 영화에는 할인정책이 적용되어 있지 않은데 할인 요금을 계산할 필요없이 영화에 설정된 기본 금액을 그대로 사용하면 됨
public class Movie { public Money calculateMovieFee(Screening screening) { if (discountPolicy == null) { return fee; } return fee.minus(discountPolicy.calculateDiscountAmount(screening)); } }
- 이 방식의 문제점은 할인 정책이 없는 경우를 예외 케이스로 취급하기 때문에 일관성 있던 협력 방식이 무너지게 된다는 것임
- 기존 할인 정책의 경우에는 할인할 금액을 계산하는 책임이
DiscountPolicy
에 있었지만 할인 정책이 없는 경우에는 할인 금액이 0원이라는 사실을 결정하는 책임이DiscountPolicy
가 아닌Movie
쪽에 있음 - 따라서 책임의 위치를 결정하기 위해 조건문을 사용하는 것은 협력의 설계 측면에서 대부분의 경우 좋지 않은 선택임 ⇒
NoneDiscountPolicy
클래스를 도입!
결론은 간단하다. 유연성이 필요한 곳에 추상화를 사용하라
구현과 관련된 모든 것들이 트레이드오프의 대상이 될 수 있다. 우리가 작성하는 모든 코드에는 합당한 이유가 있어야 한다. 비록 아주 사소한 결정이더라도 트레이드오프를 통해 얻어진 결론과 그렇지 않은 결론 사이의 차이는 크다.
코드 재사용
- 상속은 코드를 재사용하기 위해 널리 사용되는 방법이다. 그러나 객체지향 설계와 관련된 자료를 조금이라도 본 사람이라면 코드 재사용을 위해 상속보다는 합성이 더 좋은 방법이라는 이야기를 들었을 것임
상속
- 상속이 설계에 미치는 좋지 않은 영향
- 결과적으로 부모 클래스의 구현이 자식 클래스에게 노출되기 때문에 캡슐화가 약화됨
- 캡슐화의 약화는 자식 클래스가 부모 클래스에 강하게 결합되도록 만들기 때문에 부모 클래스를 변경할 시 자식 클래스도 함께 변경될 확률을 높임
- 결과적으로 상속을 과도하게 사용한 코드는 변경하기도 어려워짐
- 실행 시점에 할인정책을 바꾸려면 상속에서는 인스턴스 자체를 바꾸어야 함
- 그러나 합성을 사용하면 changeDiscountPolicy 라는 메서드를 도입하는 것만으로 할인정책을 바꿀 수 있음 ⇒
더 유연한 설계!
상속이 캡슐화를 위반한다는 것 : 상속을 이용하기 위해서는 부모 클래스의 내부 구조를 잘 알고 있어야 함. 템플릿 메서드 패턴과 같이 부모 클래스에서 해당 메서드를 어떻게 사용하는지를 잘 알고 있어야 derived class를 의 메서드 구현을 할 수 있음
설계를 유연하지 못하게 만든다는 것
상속은 부모 클래스와 자식 클래스 사이의 관계를 컴파일 시점에 결정함. 따라서 실행 시점에 객체의 종류를 변경하는 것이 불가능함


Movie avatar = new Movie("아바타", Duration.ofMinutes(120), Money.wons(10000), new AmountDiscountPolicy(Money.wons(800), ...)); avatar.changeDiscountPolicy(new PercentDiscountPolicy(0.1, ...));
합성
Movie
는 요금을 계산하기 위해DiscountPolicy
의 코드를 재사용함
- 이 방법이 상속과 다른 점은 상속이 부모 클래스의 코드와 자식 클래스의 코드를 컴파일 시점에 하나의 단위로 강하게 결합하는데 비해
Movie
가DiscountPolicy
의 인터페이스를 통해 약하게 결합된다는 점
Movie
는DiscountPolicy
가 외부에calculateDiscountAmount
메서드를 제공한다는 사실만 알고 내부 구현에 대해서는 전혀 알지 못함. 이와 같이 인터페이스에 정의된 메시지만을 통해서 코드를 재사용하는 방법을 합성이라고 함