2. 객체지향 프로그래밍
객체지향 패러다임에서 집중 해야 하는 두 가지
- 클래스 보다는 어떤 객체가 필요한지 고민하라
- 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야 한다.
도메인(domain): 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야
|
|
자율적인 객체
- 객체는 상태(State) 와 행동(Behavior) 을 함께 가진 복합적인 존재
- 객체는 스스로 판단하고 행동하는 자율적인 존재
캡슐화: 데이터와 기능을 객체 내부로 함께 묶는 것
OOP 언어들은 접근 수정자를 통해, 외부에서의 접근을 통제할 수 있는 접근 제어 매커니즘 을 제공
캡슐화와 접근 제어는 객체를 두 부분으로 나눈다.
- 퍼블릭 인터페이스: 외부에서 접근가능한 부분
- 구현(implementation): 외부에서 접근 불가능하고 오직 내부에서만 접근 가능한 부분
설계가 필요한 이유는 변경을 관리하기 위함이다.
- OOP는 의존성을 적절히 관리해서, 변경에 대한 파급효과를 제어할 수 있는 다양한 방법 제공, 대표적으로 접근 제어
협력하는 객체들의 공동체
- OOP의 장점은 객체를 이용해 도메인의 의미를 풍부하게 표현 할 수 있다는 것
- 의미를 좀 더 명시적이고 분명하게 표현할 수 있다면 객체를 사용해라
- 그 개념이 비록 하나의 인스턴스 변수만 포함하더라도 개념을 명시적으로 표현하는 것은 설계의 명확성과 유연성을 높히는 첫 걸음이다.
협력에 관한 짧은 이야기
- 객체는 다른 객체의 인터페이스에 공개된 행동을 수행하도록 request(요청) 한다.
- 요청 받은 객체는 자율적인 방법에 따라 요청을 처리하고 response(응답) 한다.
- 객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 메시지를 전송(send) 하는 것뿐이다.
- 다른 객체에게 요청이 도달할 때 해당 객체가 메시지를 수신(receive) 했다고 이야기 한다.
- 수신된 메시지를 처리하기 위한 자신만의 방법을 메서드(method) 라고 한다.
- 메시지와 메서드의 구분에서부터 다형성(polymorphism) 개념이 시작한다.
컴파일 시간 의존성과 런타임 의존성
코드 의존성(컴파일 의존성)과 런타임 의존성이 서로 다를 수 있다.
- 클래스 사이의 의존성과 객체 사이의 의존성은 다를 수 있다.
- 두 개가 다를수록 코드를 이해하기 어려워진다.
- 두 개가 다를수록 코드는 더 유연해지고 확장 가능하다
- 설계는 트레이드오프의 산물이다.
설계가 유연해질수록 코드를 이해하고 디버깅하기는 점점 더 어려워진다. (트레이드 오프)
- 반대로, 유연성을 억제하면, 재사용성과 확장 가능성은 낮아진다.
- 훌륭한 객체지향 설계자는 항상 유연성과 가독성이에서 고민해야 한다.
차이에 의한 프로그래밍
- 차이에 의한 프로그래밍(progamming by difference): 부모 클래스와 다른 부분만을 추가해서 새로운 클래스를 쉽고 빠르게 만드는 방법
상속과 인터페이스
- 상속이 가치 있는 이유는 재사용도 있지만, 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있기 때문이다.
- 인터페이스는 객체가 이해할 수 있는 메시지의 목록을 정의한다.
- 외부 객체는 자식 크래스를 부모 클래스와 동일한 타입으로 간주 할 수 있다.
- 자식 클래스가 부모 클래스를 대신하는 것을 업캐스팅(upcasting) 이라 한다.
다형성
동일한 메시지를 전송하지만 실제로 어떤 메서드가 실행될 것인지는 메시지를 수신하는 객체의 클래스가 무엇이냐에 따라 달라지는 것 다형성이라 한다.
다형성은 컴파일 시간 의존성과 런타임 시간 의존성이 다를 수 있다는 사실에 기반으로 한다.
다형성이란 동일한 메시지를 수신했을 때 객체에 타입에 따라 다르게 응답할 수 있는 능력
- 다형적인 협력에 참여할 수 있는 이유는 객체가 동일한 인터페이스를 물려 받았기 때문
다형성을 구현하는 방법은 매우 다양하지만, 메시지에 대한 실행 메서드를 컴파일이 아니라 런타임에 결정한다는 공통점이 있다.
- 지연(lazy) 바인딩, 동적(dynamic) 바인딩 이라 한다.
- 컴파일에 결정하는 것을 초기(early) 바인딩, 정적(static) 바인딩 이라한다.
- 다형성은 추상적인 개념이며, 구현 할 수 있는 방법이 상속 말고도 다양하다.
구현 상속: 서브클래싱(subclassing)
- 순수하게 코드를 재사용하기 위해서 상속하는 것
- 변경에 취약할 수 있다
인터페이스 상속: 서브타이핑(subtyping)
- 다형적인 협력을 위해 부모, 자식이 인터페이스를 공유할 수 있도록 상속하는 것
- 상속은 인터페이스 상속을 위해 사용해야 한다
추상화의 힘
추상화의 장점
- 요구사항의 정책을 높은 수준에서 서술할 수 있다. (협력 흐름을 기술한다.)
- 세부적인 내용을 무시한 채 상위 정채을 쉽고 간단하게 표현 가능
- 표현의 수준을 조정하는 것을 가능하게 한다.
- Ex) “영화 예매 요금은 최대 하나의 ‘할인 정책’과 다수의 ‘할인 조건’을 이용해 계산할 수 있다.”
- 설계가 좀 더 유연해진다.
- 설계가 구체적인 상황(가령, 예외 케이스)에 결합되는 것을 방지하기 때문
- 요구사항의 정책을 높은 수준에서 서술할 수 있다. (협력 흐름을 기술한다.)
디자인 패턴이나 프레임워크 모두 추상화를 이용해 상위정책을 정의하는 객체지향 메커니즘을 활용한다.
유연한 설계
항상 예외 케이스를 최소화하고 일관성을 유지할 수 있는 방법을 선택해야 한다.
- 일관성 있는 협력 방식이 무너지게 하지 않게 해라
결론: 유연성이 필요한 곳에 추상화를 사용하라.
코드 재사용, 상속, 합성
코드 재사용을 위해서는 상속보다 합성(composition) 이 더 좋은 방법
Movie
가DiscountPolicy
의 인터페이스를 통해 코드를 재사용하는 방법이 바로 합성- 인터페이스에 정의된 메시지를 통해서만 코드를 재사용하는 방법을 합성이라 한다.
상속은 캡슐화를 위반하고, 설계를 유연하지 못하기에 설계에 안 좋은 영향을 미친다.
- 상속하기 위해서는 부모 클래스 구조를 잘 알고 있어야 한다.
- 부모의 내부 구현이 자식에게 노출된다. 캡슐화가 약해짐
상속은 부모, 자식 관계를 컴파일 시점에 결정 => 설계가 유연해지 못하게 됨
상속보다 인스턴스 변수로 관계를 연결한 설계가 더 유연하다.
changeDiscountPolicy 함수
추가