2. 객체지향 프로그래밍
2.1. 영화예매 프로그래밍
→ skip
2.2. 객체지향 프로그래밍을 향해
협력, 객체, 클래스
객체지향 프로그램을 작성할 때 가장 먼저 고려하는 것은 무엇인가? C++, 자비 루비, C#과 같이 클래스 기반의 객체지향 언어에 익숙한 사람이리면 가장 먼저 어떤 클래스(class)가 필요한지 고민할 것이다. 대부분의 사람들은 클래스를 결정한 후에 클래스에 어 떤 속성과 메서드가 펼요한지 고민한다. 안타깝게도 이것은 객체지향의 본질과는 거리가 멀다. 객체지향은 말 그대로 객체를 지향하는 것이다. - p. 40
→ 클래스와 객체의 차이점은 뭘까? 보통 둘을 동일한 의미로 사용하고 있지 않나?
→ Class and Object definition in Java
object
: An object stores its state in fields (variables in some programming languages) and exposes its behavior through methods (functions in some programming languages).class
: A class is the blueprint from which individual objects are created.
객체에 초점을 맞추라
- 클래스보단 어떤 객체가 필요한지 고민하라.
- 클래스는 특정 상태와 행동을 공유하는 객체를 추상화 한 것
- 클래스를 정의하려면 특정 객체의 상태와 행동을 먼저 정의해야 함
- 객체는 기능 구현을 위해 협력하는 공동체의 일원으로 봐야함
- 객체는 다른 객체와 상호작용하는 협력적인 존재임
- 공동체의 일원으로 바라보면 설계가 유연해지고 확장 가능해진다 ??
- 객체의 윤곽이 잡히면 클래스를 구현하라 ??
→ 클래스를 정의하는 것이 객체 정의와 다른 점이 뭘까?
도메인의 구조를 따르는 프로그램 구조
도메인: 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야(지식)
- 객체지향 패러다임은 프로그램의 전 라이프사이클에서 객체 라는 동일한 추상화 기법을 사용할 수 있다.
- 클래스의 이름은 도메인의 이름과 유사하게 명명해야 한다.
- 프로그램의 구조를 이해하고 예상하기 쉽다.
클래스 구현하기
- 인스턴스 가시성: 변수는
private
으로, 메서드는public
으로 - 클래스의 경계 구분짓기
- 객체의 자율성을 보장
- 프로그래머에게 구현의 자유를 제공: 인터페이스는 지키되 구현은 프로그래머의 판단 하에!
자율적인 객체
- 객체는 상태(state) 와 행동(behavior) 을 가지는 복합적인 존재
- 객체가 스스로 판단하고 행동하는 자율적인 존재
- 절차지향 패러다임에선 데이터와 기능을 엮어 프로그램을 구성
- 데이터와 기능은 하나의 단위로 묶이지 않는다.
- 객체와는 다르다. 데이터와 기능은 개별적인 존재
- 객체지향은 데이터와 기능을 객체 라는 단위로 묶는다 → 캡슐화
- 객체지향 패러다임의 언어는 캡슐화에 더 나은 기능을 제공한다. (e.g. Java의 접근 수정자)
- 접근 통제의 이유: 객체를 자율적인 존재로 만들기 위해
- 객체 스스로 상태를 관리하기 위해 외부 간섭을 최소화(외부에서 임의로 상태변경 할 수 없게)
- 캡슐화는 객체를 두 부분으로 나눈다.
- Public Interface: 외부에서 접근 가능한 부분
- Implementation: 내부에서만 접근 가능
- 인터페이스와 구현의 분리 (Seperation of Interface and Implementation)
→ Spring MVC 패턴에 따라 정의된 객체는 저자가 말한 객체라고 볼 수 있을까?
- 상태가 없다. 있으면 안된다.
프로그래머의 자유
- 프로그래머의 역할을 둘로 나눔
- 클래스 작성자: 새로운 데이터 타입을 프로그램에 추가
- 클라이언트 프로그래머에게 필요한 부분만 공개하고 나머지는 감춘다
- 클라이언트 프로그래머: 클래스 작성자가 추가한 데이터를 사용
- 필요한 클래스들을 엮어 애플리케이션을 빠르게 구축
- 클래스 작성자: 새로운 데이터 타입을 프로그램에 추가
- 왜 숨길까?
- 클라이언트 프로그래머에 대한 영향도를 신경쓰지 않아도 된다. (영향이 없으니까)
- 숨겼으니 내부 구현을 마음대로 변경할 수 있다. (퍼블릭 인터페이스만 그대로 동작한다면!)
- → 구현의 은닉 (Implementation Hiding)
→ Java 프로그래머는 Logback의 내부 구현을 알지 못해도 사용할 수 있다. 스펙(인터페이스)대로 동작한다고 기대하고 필요한 인터페이스가 잘 동작하면 된다.
- 설계는 변경을 관리하기 위해 필요하다.
협력하는 객체들의 공동체
- 협력: 기능을 구현하기 위해 객체들 사이에 이뤄지는 상호작용
협력에 관한 짧은 이야기
- 객체는 다른 객체의 공개된 행동(public interface)을 수행하도록 요청 한다.
- 메시지를 전송 (send a message)
- 요청받은 객체는 자율적으로 요청을 처리한 후 응답 한다.
- 메시지를 수신 (receive a message)
- 메서드: 수신한 메시지를 처리하기 위한 객체 자신의 방법
- 메시지와 메서드를 구분하는 것은 중요하다. 왜??
- 메시지와 메서드의 구분에서 다형성 이 출발한다.
- 동적타입 언어에선 시그니쳐가 다른 메서드를 통해서 메시지에 응답할 수 있다.
2.3. 할인요금 구하기
할인요금 계산을 위한 협력 시작하기
calculateMovieFee()
의 이상한 점- 할인 정책(
discountPolicy
)을 결정하는 코드가 없다.
- 할인 정책(
- 상속(Inheritance)과 다형성(Polymorphism), 그리고 추상화(Abstraction)
할인 정책과 할인 조건
- 추상 클래스
DiscountPolicy
는 할인 여부/요금 계산의 전체 프로세스를 정의하지만 요금 계산하는 부분은 추상 메서드인getDiscountAmount()
에게 위임 → Template Method 패턴
할인 정책 구성하기
- 클래스 생성자로 도메인의 제약을 구현
- 올바른 상태를 가진 객체 생성을 보장한다
→ Dependency Injection 얘기는 Out of Scope?
2.4. 상속과 다형성
컴파일 시간 의존성과 실행 시간 의존성
-
Movie
클래스는DiscountPolicy
추상클래스에 의존하고 있다. -
실제 필요한 클래스는 구현체
-
실행(runtime) 시에 구현체 인스턴스에 의존해야 함
-
런타임에 협력가능한 이유?
-
코드 의존성(compile time)과 실행시점 의존성(runtime)은 다를 수 있다. → 객체지향의 특성
- 유연하고 쉽게 재사용 가능
- 코드를 이해하기 어려워진다.
- → 코드를 이해하기 위해 구현체를 헤맨적이 없는지?
-
설계의 trade off
- 설계가 유연해지면 코드 디버깅이 어려워 진다.
- 유연성이 억제되면 재사용성과 확장성이 낮아진다.
차이에 의한 프로그래밍
- 상속: 코드를 재상용하기 위해 널리 사용되는 방법
- 관계 설정으로 부모 클래스의 속성과 행동을 포함시킬 수 있다.
- 부모 클래스의 구현은 공유하되 다른 행동을 정의할 수 있다. (overriding)
→ 차이에 의한 프로그래밍 (programming by difference)
상속과 인터페이스
- 상속의 장점: 부모의 모든 인터페이스를 물려받을 수 있다.
- 사람들은 메서드나 프로퍼티 재사용 때문이라는데?
- 인터페이스는 객체가 이해할 수 있는 메시지의 목록
- 자식 클래스는 부모 클래스가 수신할 수 있는 모든 메시지를 수신할 수 있다.
- 동일한 타입으로 간주할 수 있다!!!
- 생성자 인자가 추상 클래스임에도 런타임에서 구현체를 전달할 수 있는 이유 (Upcasting)
다형성
- 메시지와 메서드는 다르다
- 요청자는
calculateDiscountAmount
메시지를 보내지만 연결된 객체에 따라 실행하는 메서드는 달라진다.- 추상클래스와 구현 클래스의
calculateDiscountAmount
메서드는 모두 다르다!
- 추상클래스와 구현 클래스의
- 다형성
- 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력을 의미한다.
- 컴파일 타임과 런타임의 의존성이 다르다는 것에 기반한다.
- 다형적인 협력에 참여하는 객체들은 모두 같은 메시지를 이해할 수 있어야 한다. → 인터페이스가 동일하다
- 메시지와 메서드를 실행시점에 바인딩 → Lazy Binding, Dynamic Binding
- 클래스 상속이 다형성의 전부가 아니다.
- 구현 상속과 인터페이스 상속
- 구현 상속: 코드 재사용을 위해
- 인터페이스 상속: 다형적인 협력을 위해
- 구현 상속은 변경에 취약한 코드를 생성한다. → 가격변경이력...
인터페이스와 다형성
- 순수하게 인터페이스만 공유하고 싶을 때? Java는
interface
를 사용
2.5. 추상화와 유연성
추상화의 힘
- 인터페이스에 초점을 맞춘다
- 추상화 계층만 보면 요구사항을 높은 수준에서 서술할 수 있다.
- 설계가 유연해진다.
유연한 설계
- 예외케이스를 최소화하고 일관성을 유지할 수 있는 방법을 고려하라
- 유연성이 필요한 곳에 추상화를사용하라.
추상 클래스와 인터페이스 트레이드오프
- 설계의 트레이드 오프를 항상 염두해야 한다.
코드의 재사용
- 상속과 합성
상속
- 코드를 재사용할 수 있다.
- 캡슐화를 위반한다.
- 설계 유연성이 떨어진다. (변경이 어려워진다.)
합성
- 인터페이스를 통해 약하게 결합된다.
질문거리
- 클래스와 객체의 차이점은 뭘까? 보통 둘을 동일한 의미로 사용하고 있지 않나?
- 클래스를 정의하는 것이 객체 정의와 다른 점이 뭘까?
- Spring MVC 패턴으로 정의된 인스턴스는 저자가 말한 객체라고 볼 수 있을까?
- 내부 상태가 없다. (있으면 안된다)