1장 협력하는 객체들의 공동체
객체지향에서 중요한 세가지 개념: 역할, 책임, 협력
- 요청을 하고 응답을 하고...: 협력
- 협력을 하기 위해서는 역할이 있어야. 그런데 역할은 곧 책임을 내포
- 여러 사람이 동일한 역할을 수행 가능
- 역할은 대체 가능성(같은 역할을 지닌 다른 사람으로)
- 책임을 수행하는 법은 자율적으로. 알아서 역할만 수행하면.: polymorphism
- 한 사람이 동시에 여러 역할을 수행할 수 있음: 여러 interface implemets
객체지향 프로그램에는 객체가 한다.
- 객체는 서로 메시지를 주고받으며 협력
- 객체의 미덕:
- 객체는 충분히 협력적이어야. 다른 객체의 요청에 귀기울이고 도움을 요청할 수 있어야 한다.
- 객체는 충분히 자율적이어야.
객체지향에서 중요한건 클래스가 아니라(물론 중유하지만...) 책임, 역할, 협력이다.
2장 이상한 나라의 객체
- 원래 사람은 객체를 이용해 본다?
- 세상살이의 복잡성을 줄이기 위해.
- 길, 길가는 자동차, 태양...
- 객체지향 패러다임의 목적은 현실세계를 모방하는 것이 아니라 현실세계를 기반으로 새로운 세계를 만드는 것
객체는 상태, 생동, 식별자를 가진다
객체란 식별 가능한 개체 또는 사물이가다. 객체는 자동차처럼 만질 수 있는 구체적인 사물일 수도 있고, 시간처럼 추상적인 개념일 수도 있다. 객체는 구별 가능한 식별자, 특징적인 행동, 변경 가능한 상태를 가진다. 소프트웨어 안에서 객체는 저장된 상태와 실행 가능한 코드를 통해 구현된다. p.47
상태
왜 필요한가
- 객체가 주변 환경과 상호작용하면서 어떻게 변하는가.
- 어떤 행동의 결과는 과거에 어떤 행동들이 일어났는가에 의존.
- 그런데 과거의 행동을 모두 기억하는 것은 힘들다.
- 따라서 과거 행동들의 과정과 결과를 단순하게 기술하기 위해 상태라는 개념을 고안.
상태와 프로퍼티
- 세상의 모든 것이 객체인것은 아니다.
- 예를들어 앨리스의 키, 음료의 양 같은 것.
- 이것들은 독립적이기보다 다른 객체의 특성, 즉 상태를 표현하는데 사용.
- 또 때로는 상태를 표현하기 위해 다른 객체를 사용할 때도 있다.
- 일반적으로 프로퍼티(앨리스의 경우 키, 위치 등)은 정적이다.
- 반면 property value(앨리스의 키 값, 위치 값)등은 동적
- 객체와 객체 사이의 의미있는 연결을 링크(link)라고 하자
- 객체 사이에는 링크를 통해서만 메시지를 주고받을 수 있다.
생태는 특정 시점에 객체가 가지고 있는 정보의 집합으로 객체의 구조적 특징을 표현한다. 객체의 상태는 객체에 존재하는 정적인 프로퍼티와 독적인 프로퍼티 값으로 구성된다. 객체의 프로퍼티는 단순한 값(속성, attribute)와 다른 객체를 참조하는 링크로 구분할 수 있다. p.51
- 객체는 자율적이다. 그리고 자신의 상태에 칙임을 져야 한다. 객체가 다른 객체의 상태를 직접 주무를 수 없다.
행동
-
객체가 행동을 하면 상태가 변한다. 즉 side effect를 초래. - 객체의 행동은 상태에 영향을 받는다.(키큰 앨리스는 문을 통과할 수 없다.) - 객체의 행동은 상태를 변경시킨다.(음료를 마시면 앨리스틑 커진다.)
행동이란 외부의 요청 또는 수신된 메시지에 응답하기 위해 동작하고 반응하는 활동이다. 행동의 결과로 객체는 자신의 상태를 변경하거나 다른 ㅊ객체에게 메시지를 전달할 수 있다. 객체는 행동을 통해 다른 객체와의 협력에 참여하므로 행동은 외부에 가시적이어야 한다. p.55
-
상태를 캡슐화 해야한다.
- 앨리스가 을료수를 마실 때 쓰는
drinkBeberage()
를 보고 앨리스의 키가 바뀐다고 유추할 수 없다.
- 앨리스가 을료수를 마실 때 쓰는
식별자
- 값은 immutable하다. 즉 두개가 같으면 같다.
- 객체는 시간에 따라 변경되는 상태를 포함하며 행동은 상태를 변경한다. 즉 객체는 mutable state를 가진다고 할 수 있다.
- 두개 객체는 같아도 다르고 달라도 같을 수 있다.
- 어제의 나와 오늘의 나의 상태는 다르지만 같은 객체이다.
- 반대로 복제인간은 상태는 같지만 다른 객체이다.
- 그래서 객체에게는 동일성을 판단할 수 있는 식별자가 필요하다.
식별자란 어떤 객체를 다른 객체와 구분하는 데 사용하는 객체의 프로퍼티다. 값은 식별자를 가지지 않기 때문에 상태를 이용한 동등성 검사를 통해 두 인스턴스를 비교해야한다. 객체는 상태가 변경될 수 있기 때문에 식별자를 이용한 동일성 검사를 통해 두 인스턴스를 비교할 수 있다. p.58
행동이 상태를 결정한다.
- 객체를 만들 때 상태를 먼저 결정하면
- 캡슐화가 저해된다. 생태가 객체 내부로 갭술화되지 못하고 공용 인터페이스에 그대로 노출되기 십상이다.
- 객체를 협력자가 아닌 고립된 섬으로 만든다.
- 객체의 재사용성이 저하된다. 상태에 초점을 맞춘 객체는 다양한 협력에 참여하기 어렵기 때문
- 그래서 인터페이스 기반의 프로그램을 만들어야 한다.
3장 타입과 추상화
-
세상은 복잡하기 때문에 추상화시켜 좀 더 단순하게 살아보자
-
지하철 노선도를 보면 지형같은 쓸데 없는/관심 없는 것은 배제하고 필요한 것만 있다: 추상화
-
현실 세계와 물리적 모델링
- 공던지기 포물선 문제: 공의 색이나 저항은 무시하자
-
그룹으로 나누어 단순화시키기
- 농구공, 축구공, 배구공들을 공으로 퉁치자.
- 나, 너, 김씨, 박씨, 이씨를 사람으로 퉁치자.
-
어떻게 퉁칠수 있을까?
-
공통된 부분이 있고 우리의 관심사는 공통된 부분이기 때문에.
-
객체들을 추상화해서(부분을 잘 무시해서) 하나로 퉁친 것을 개념(concept)이라고 부르자.
객체란 특정한 개념을 적용할 수 있는 구체적인 사물을 의미한다. 개념이 객체에 적용됐을 때 객체를 개념의 인스턴스라고 한다. p.84
개념의 세 가지 관점
-
심볼: 개념을 가르키는 이름
-
내연(intension): 개념의 정의. "행성은 항성을 도는 것"
-
외연(extension): 개념에 속하는 모든 객체의 집합. "지구는 행성."
-
어떤 객체를 어떤 개념에 적용할지 정하는 건 중요한 일. 이를 분류라고 하다.
타입
- 개념의 있어보이는 말, "타입"
- 프로그램의 관점에서 타입은 0과 1의 배열을 어떻게 해석할 지
- 타입에 따라 적용할 수 있는 operator가 다르다. -보면 타입으로 가리면 뒤쪽의 0과 1은 안보인다.
객체와 타입
- 객체는 데이터인가: ㄴㄴ
- 객체에서 중요한 것은 객체의 행동. 상태는 부수효과를 처리하기 위해 도입한 개념.
- "책임, 역할, 협력" 중에 상태는 없다.
- 어떤 객체가 어떤 타입에 속하는지 결정하는 것은 객체가 하는 행동.
- 객체 내부는 밖에서 안보인다.
- 자판기 안에서 사람이 커피타도 ㄱㅊ
다시한번 말한다; 객체의 타입은 행동에 의해 결정된다.
타입의 계층
- 타입에도 계층관계가 있다.
- 머그컵은 컵이다. 그런데 손잡이로 잡을 수 있는.
- 머그컵의 외연은 컵의 외연의 부분집합니다.
- 이 둘의 관계를 일반화/특수화(generalization/specialization) 관계라고 하자.
- 타입의 단어로는 supertype/subtype.
- 다시한번 말한다; 객체의 타입은 행동에 의해 결정된다.
- 둘의 관계는 행동에 의해 결정된다.
- 수퍼타입이 할 수 있는 것은 서브타입도 할 수 있다:
- 교체할 수 있다.
정적타입
- 그래서 왜 타입이 필요할까?
- 바뀌는 것을 생각하기에는 너무 힘들다.
- 안바뀌는 것을 생각하는 것은 좀 더 쉽다.
- 바뀌는 객체(1m의 앨리스, 4m의 앨리스...)를 정적인 모습(키가 있는 앨리스)로 추상화하자.
- 그렇다. 타입은 객체의 상태변화라는 요소를 제거하고 정적으로 추상화 한 것이다.
동적 모델과 정적 모델
- 객체를 생각할 때 상태가 바뀌는 스냅샷으로 생각하던가
- 시간에 독립적인 정적 모델로 생각하던가.
- 애플리케이션을 만들기 위해서는 둘을 섞어써야한다.
클래스
- 타입을 구현하는 가장 보편적인 방법은 클래스
- 주의: 구현하는. 클래스는 타입이 아니다. JS에서는 프로토타입 기반
4장 역할, 책임, 협력
객체 + 객체 > 2*객체
가 되어야 한다.- 객체끼리 협력을 잘 해야된다는 말.
협력
- 객체는 다른 객체에게 요청하고 그 객체는 요청에 응답해준다.
- 또는 객체는 다른 객체의 요청에 응답할 책임이 있다.
- 누군가 "힘에는 책임이 따른다"고 말한거 같은데...
책임
- 객체의 책임은 객체가 알아햐 할 정보와 객체가 수행할 수 있는 행위에 대해 서술한 문장.
- 다른 말로 책임은 아는 것과 하는 것의 서술
- 하는 것
- 객체를 생성하거나 계산을 하는 등 스스로 하는 것
- 다른 객체의 행동을 시작하는 것
- 다른 객체의 활동을 제어하고 조절하는 것
- 아는 것
- 개인적인 정보에 관해 하는 것
- 다른 객체에 관해 아는 것
- 자신이 유도하거나 계산할 수 있는 것에 관해 아는 것
- 하는 것
- 설계 초반에는 각 객체의 객임과 협력의 구조를 대략 짜는 것이 중요.
- 메소드나 구현 방법은 나중에 생각하자
역할
- 협력의 관점에서 객체가 책임의 집합을 수행한다는 것은 무슨말일까
- 김씨는 '판사'라는 역할을 수행하고 있다. 이씨는 '피고'의 역할을 수행한다.
- 어떤 객체가 수행하는 책임의 집합은 협력 안에서 수행하는 역할을 암시한다.
- 더불어 김씨를 '판사'라고 부름으로써 박씨로 교체할 수도 있다.
역할이 답이다
- 협력에 참가하는 과정들(판사는 재판, 피고는 증언, 변호사는 변호...)이 너무 유사해서 하나의 협력으로 다루고 싶다.
- 이제 판사라는 "역할", 피고라는 "역할"을 부여하면 다른 협력을(김씨가 판사일때, 박씨가 판사일때) 하나로 다룰 수 있다.
- 다른말로 판사 자리는 김씨가 해도 되고 박씨가 해도 된다는 말
- 유사한 협력을 역할로 추상화하는 것.
- 다양한 객체들이 협력에 참여할 수 있다. 역할은 객체지향 설계의 단순성, 유연성, 재사용성을 뒷받침하는 핵심 개념.
- 객체는 주어진 역할 이외에 다른 일도 할 수 있다: 김씨는 밥도 먹을 수 있다.
어떻게 설계해야할까
- 객체를 섬으로 바라보던 습관을 버려라.
- 협력을 먼저 생각하고 협력에 참여하기 위한 책임을 설계한다. 그 후 행동을 수행하는데 필요한 데이터를 고민해야한다.
5장 책임과 메시지
- 객체는 명확한 책임과 역할을 가지고 협력에 참여해야한다.
- 방관자 효과
- 객체는 자율적이어야 한다.
- 자율적인 객체는 스스로 정한 원칙에 따라 판단하고 스스로의 의지를 기반으로 행동한다.
- 객체가 자율적이기위해서는 객체에게 할당되는 책임의 수준 역시 자율적이어야한다.
- 너무 상세하면 객체의 자율성을 해친다
- "시간순으로, 말로 간결하게 증언하라"는 너무 상세해 객체의 자유를 해친다.
- 책임이 너무 포괄적이어도 안좋다.
- "증언하라"말고 "설명하라"라고 하면 책임이 명확하지 않다.
- 자율적인 책임의 특징은 '어떻게'가 아니라 '무엇을' 해야하는가를 설명한다.
메시지와 메서드
- 객체끼리는 메시지를 통해서만 이야기한다.
피고.증언하라(어제)
같이 메시지에는 수신자, 메시지이름, 인자가 들어간다.
다형성
- 다형성은 동일한 역할을 수행할 수 있는 객체들 사이의 대체 가능성
- 피고 김씨는 회상하며 증언하고 피고 이씨는 써온것을 읽으며 증언할 수 있다. 중요한 것은 둘다 증언한다는 것
- 다형성을 이용하면
- 협력이 유연해진다. 송신자는 수신자가 메시지를 이해한다면 누구라도 상관하지 않는다.
- 협력이 수행되는 방식을 확장할 수 있다. 수신자를 교체해도 판사와 피고사이에는 "증언하라"라는 관계밖에 없다.
- 협력이 수행되는 방식을 재사용할 수 있다.
- 재판은 한국에서도, 미국에서도, 가나에서도 재사용할 수 있다.
메시지를 따르라
-
객체지향 애플리케이션은 클래스가 아니라 메시지를 통해 정의된다.
-
메시지를 중심에 두자.
-
묻지 말고 시켜라
- 객체에게 생태를 물어보지 말고 그냥 시켜라.
- 메시지는 무엇을 해야하는지에 대한 요청이고 자율적인 객체는 메시지를 알아서 처리한다.
-
메시지를 믿어라
인터페이스
- 인터페이스를 쓰면
- 인터페이스만 알고 있으면 내부는 몰라도 된다.
- 인터페이스를 바꾸지 않고 내부 구성이나 동작 방식만 변경할 수 있다.
- 대상이 변경되도 인터페이스만 호환되면 된다.
- 메시지가 인터페이스를 결정한다.
- 인터페이스는
- 좀 더 추상적이야한다. (그러나 적당히)
- 최소여야한다. 외부에서 사용하지 않는 인터페이스는 노출하지 말자
- 구현에 차이가 있음을 인식해야한다.
- 인터페이스와 구현의 분리는 캡슐화를 도운다.
6장 객체지도
- 길을 물어볼 때 가는 방법을 물어보면 다른데로 갈 때 또 물어봐야
- 반면 지도보는 법을 물어보면 계속 쓸 수 있다.
- 소프트웨어는 기능과 구조의 영역 두가지가 있다.
- 기능이 좋은 소프트웨어의 충분조건이라면 구조는 좋은 소프트웨어의 필요조건이다.
- 소프트웨어는 계속 바뀐다.
- 미래의 변경은 예상할 수 없기에 변경에 유연하게 대처할 수 있어야한다.
- 소프트웨어 설계를 자주 변경되는 기능이 아니라 잘 변경되지 않는 구조 기반으로 하면 좋다.
- 단순하면서 유연한 설계를 창조하는 것은 공학이라기보다 예술에 가깝다.
- 미래를 대비하자
기능과 구조
- 객체지향 세계를 구축하기 위해서는 사용자에게 제공할 '기능'과 기능을 담을 안정적인 '구조'가 있어야한다.
- 이 두 재료를 어디서 구할까?
- 구조는 사용자나 이해관계자들이 도메인에 관해 생각하는 개념과 개념들 간의 관계로 표현한다.
- 기능은 사용자의 목표를 만족시키기 위해 책임을 수행하는 시스템의 행위로 표현한다.
- 단어장
- 유스케이스 모델링: 기능을 수집하고 표현하기 위한 기법
- 도메인 모델링: 구조를 수집하고 표현하기 위한 기법
안정적인 재료: 구조
- 사람들은 자기가 관심있는 문제를 해결하기 위해 소프트웨어를 사용한다.
- 게이머는 심심해서, 은행은 돈 관리를 위해
- 이처럼 사용자가 프로그램을 사용하는 대상 분야를 도메인이라고 한다.
- 모델은 대상을 단순화해서 표현한 것. 즉, 대상의 추상화
- 따라서 도메인 모델이란 사용자가 프로그램을 사용하는 대상 영역에 관한 지식을 선택적으로 단순화하고 의식적으로 구조화한 형태.
- 도메인 모델은 이해관계자들이 바라보는 멘탈 모델
- 도널드 노먼 왈: 제품을 설계할 때 제품에 관한 모든 것이 사용자들이 제품에 가지고 있는 멘탈 모델과 정확하게 일치해야한다.
- 도메인 모델이란 사용자가 도메인을 바라보는 관점이며, 설계자가 시스템구조를 바라보는 관점이며, 수프트웨어 안에 구현된 코드의 모습 그 자체이다.
- 객체지향을 이용하면 도메인에 대한 사용자 모델, 디자인 모델, 시스템 이미지가 모두 유사하게 만들 수 있다.
불안정한 재료: 기능
- 유스케이스는 사용자와 시스템 간의 상호작용의 흐름을 글로 정리한 것
- 유스케이스의 가치는 사용자들의 목표를 중심으로 시스템의 기능적인 요구사항들을 이야기 형식으로 묶을 수 있다는 점이다.
- 산발적으로 흩어져 있는 기능에 사용자 목표라는 문맥을 제공해 각 기능이 유기적인 관계를 지닌 체계를 이룰 수 있게 한다.
예
-
유스케이스명: 중도 해지 이자액을 계산한다.
-
일차 액터: 예금주
-
주요 성공 시나리오:
- 예금주가 정기예금 계좌를 선택
- 시스템은 정기예금 계좌 정보를 보여준다.
- 예금주가 금일 기중느로 예금을 해지할 경우 지급받을 수 있는 이자 계산을 요청한다.
- 시스템은 중도 해지 시 지급받을 수 있는 이자를 계산한 후 결과를 사용자에게 제공한다.
-
확장: 3a. 사용자는 해지 일자를 다른 일자로 입력할 수 있다.
-
유스케이스는
- 사용자와 시스템 간의 상호작용을 보여주는 텍스트이다. 다이어그램에 너무 힘 빼지 말라
- 여러 시나리오의 집합
- 단순한 피처 목록과 드르다.
- 사용자 인터페이스와 관련된 세부 정보를 포함하지 말아야한다. 왜냐하면 사용자 인터페이스는 자주 바뀌기 때문에.
- 내부 설계와 관련된 정보를 포한하지 않는다.
재료합치기
- 책임-주도 설계는 이 지점부터 적용
- 유스케이스는 사용자에게제공할 기능을 시스템의 책임으로 보개함으로써 객체 간의 안정적인 구조에 책임을 분배할 수 있는 출발점 제공.
- 도메인 모델은 기능을 수용하기 위해 은유할 수 있는 안정적인 구조를 제공
- 책임-주도 설계는 시스템의 기능을 역할과 책임을 수행하는 객체들의 협력 관계로 바라보게 함으로 써 유스케이스와 도메인 보델을 통합한다.
7장 함께 모으기
- 유스케이스 작성
- 도메인 찾기
- 협력 찾기
- 메시지 추출
- 인터페이스 정리
- 메시지의 책임자/참여자 할당
- 구현
부록
클래스
- 객체지향 프로그래밍언어에서 타입을 구현하는 가장 보편적인 방법은 클래스를 이용하는 것
- 그러나 클래스와 타입이 동일한 개념은 아니다.
- 클래스는 타입을 구현하는 용도 외에 코드를 재사용하는 용도로 사용되기도 한다.
- 클래스 외에도 추상 클래스나 인터페이스를 이용해 타입을 수현할 수도 있다.
상속
- 프로그래밍언어에서 일반화/특수화 관계를 구현하는 가장 일반적인 방법은 클래스 간의 상속을 이용하는것
- 그러나 모든 상속 관계가 일반화/특수화 관계는 아니다.
- 한 타입이 다른 타입의 서브타입이 되기 위해서는 수퍼타입에 순응(conformance)해야한다. 또는 대체할 수 있다.
- 구조적 순응: 내연과 관련된 100% 규칙. 서브타입은 슈퍼타입이 가지고 있는 속성과 연관관계면에서 100% 일치해야한다. Person이 name 속성을 가지면 Employee역시 name 속성을 가질것이라고 기대할 수 있다.
- 행위적 순응: 타입의 행위에 관한 것. 리스코프 치환 원칙. Person이 getAge()라는 메시지에 나이를 응답한다면 Employee도 getAge()에 나이를 응잡한다.
- 상속의 다른 용도는 코드 중복을 방지하고 공통 코드를 사용 하는 것.
반복되는 말들
- 클래스가 아니라 메시지를 중심에
- 객체를 섬이 아니라 협력이라는 문맥 속에서 봐야한다.
후기
- 객체, 도메인 등등 이 책에 나오는 대부분의 것은 추상화에 대한 것
- 추상화는 복잡한 세상을 이해할만 하게 만들어준다.
- 6장은 아직은 추상적인 이야기.