[BOOK]객체지향의 사실과 오해, 객체지향 사고 프로세스
우연히 객체지향 관련 도서를 추천 받아서 읽게 되었다.
Intro
사실 Python이라는 프로그램밍 언어를 다루면서 객체지향에 관하여 무수한 많은 이야기를 보았지만 객체지향이 대체 무엇인지 알지못했고, 필요성도 제대로 느끼지 못했었다.
객체지향 언어를 배운다면서 객체지향을 모른다는 것은 모순이 되지 않겠는가?
아래 내용은 책에 대한 내용을 개인적으로 정리한 내용이다.
객체지향의 사실과 오해
01. 협력하는 객체들의 공동체
객체지향이라는 것은 실세계의 투영이며 객체란 현실 세계의 사물에 대한 추상화이다.
객체지향의 목표는 실세계의 모방이 아닌 새로운 세계를 창조, 소프트웨어 시스템이 해결하려고 하는 실재는 잘해봐야 먼 친척 밖에 되지않는다.
객체지향이 기존 프로세스와는 객체는 상태와 행위를 지니고 있다(즉, 묶는 것)
라는 점에서 다른 형태이고 의사소통의 방법은 메시지(Massage) 한가지
이다.(여기서 메시지를 처리하는 방법을 메소드(Method)
라 함.)
객체지향이란 상호작용하는 자율적인 객체들의 공동체
이다.
행위를 구현하기 위해 다른 객체와 협력하고 협력 내에서 정해진 역할을 수행하며 역할은 책임의 집합
이다. 또한 협력을 위해 메시지를 전송하고 메시지를 처리하는데 메서드를 자율적으로 선택한다.
Edward sapir와 benjamin lee whorf가 주장한 사피어-워프 가설 ‘언어가 인간의 사고를 지배한다’는 언어결정론. 에스키모인들이 눈을 지칭하는 많은 어휘가 존재하기 때문에 한국인에 비해 눈에 관해 좀 더 상세하고 정확하게 생각할 수 있다는 것이다.
객체지향의 핵심은 클래스(객체들의 협력관계를 코드로 옮기는 도구에 불과함)가 아니다. 핵심은 적절한 책임을 수행하는 역할간의 유연하고 견고한 협력관계를 구축하는 것
. 클래스의 구조와 메서드가 아니라 객체의 역할, 책임, 협력에 집중하라.
객체지향은 말그대로 객체를 지향하는 것이지 클래스를 지향하는 것이 아니다.
언어는 지식의 한 종류로 언어결정론에서의 내용은 결국 지식이 인간의 사고를 결정한다는 뜻이 아닐까? 생각한다. 이는 아는 것이 많을 수록 생각의 확장이 이뤄져 조금 더 복잡하고 다양한 생각이 가능하다는 결론이라 생각한다.
02. 이상한나라의 객체(객체지향과 인지능력)
세상을 더 작은 객체로 분해하는 것은 본질적으로 세상이 포함한 복잡성을 극복하려는 인간의 몸부림이다.
그러나 객체지향 패러다임에서 현실과 소프트웨어 사이의 유사성은 여기까지 일 뿐. 이 패러다임의 목적은 현실 세계를 모방하는 것이 아닌 현실세계를 기반으로 새로운 세계를 창조하는것. 따라서 현실세계와 다른 모습을 보이는 것은 일반적이다. 그래서 객체지향 애플리케이션의 내부를 볼 수 있다면 우리가 알고 있는 세계와 유사해 보이지만 본질적으로 매우 이질적인 모습에 토끼를 뒤쫓던 소녀가 경험한 이상한 나라처럼 낯설고 기묘할 것이다.
상태를 먼저 결정하고 행동을 나중에 결정하는 방법은 설계에 나쁜영향을 미친다.
첫째, 상태를 먼저 결정하면 캡슐화가 저해(상태가 깔끔하게 캡슐화되지 않고 공용인터페이스에 노출이됨) 둘째, 객체를 협력자가 아닌 고립된 섬으로 만든다.(객체는 애플리케이션의 문맥내에서 다른객체와 협력하기 위해서) 셋째, 재사용성 저하 (객체의 상태를 조회하는 작업(쿼리:query), 객체의 상태를 변경하는 작업(명령:command))
따라서 객체가 적합한지 결정하는 것은 그 객체의 상태가 아닌 행동(즉, 행동이 상태를 결정한다
)이다. 객체지향 설계는 애플리케이션에 필요한 협럭을 생각하고 협력에 참여하는데 필요한 행동을 생각한 후 행동을 수행할 객체를 선택하는 방식으로 수행된다. 협력안에서 객체의 행동은 결국 책임을 의미하고 어떤책임이 필요한가를 결정하는 과정이 전체설계를 주도하는 책임-주도 설계
이다.
객체지향세계에서의 객체는 현실세계에서의 객체보다 더 많은 능력이나 특징을 보유할 수 있다.
객체지향세계는 현실과 전혀 다른 세계를 창조하는 것이지 현실의 추상화가 아니다. 따라서 은유를 활용해 표현적 차이(사람들이 생각하는 모습과 실제 소프트웨어의 표현 사이의 차이)를 줄일 수있다. 그래서 현 도메인에 활용하는 이름을 객체에 부여하라는 지침이 그 이유이다.
객체지향의 핵심은 인지능력이다. 예를 들어, 팔을 움직인다라는 것을 객체지향적인 사고방식으로 표현한다면 ‘팔을 움직인다’ 라는 클래스 안에 ‘특정 근육을 수축한다’ 라는 것이 들어가있다. 이것을 바라보는 관점은 사람마다 다를 수 있기에 인지능력이 중요하다고 생각된다.
03. 타입과 추상화
해리백이 창조한 현재의 지하철노선도는 구시대의 노선도(실제 지도로 구불구불함)에서 지도가 가져야할 정확성을 버리고 목적성에 치중한 결과이다.(지하철을 추상화)
현실세계의 복잡한 현상을 파악하기 위해서는 현실을 이해해야한다. 따라서 사람들은 본능적으로 이해하기 쉽고 예측 가능한 수준으로 현실을 분해하고 단순화, 추상화의 목적은 현실에서 출발하되 불필요한 부분을 도려내가면서 사물의 놀라운 본질을 드러나게하는과정이다.
리처드 파인 ‘현상은 복잡하다. 법칙은 단순하다. 버릴게 무엇인지 알아내라’
복잡성을 다루기 위해 추상화는 두차원에서 이루어진다. 하나는 사물들 간의 공통점은 취하고 차이점은 버리는 일반화를 통해 단순하게하는 것. 둘째는 중요한 부분을 강조하기 위해 불필요한 세부 사항을 제거하는 것.(즉, 목적은 복접상을 이해하기 쉬운 수준으로 단순화하는것)
앨리스가 트럼프 병사들이 입장할때 트럼프에 불과해 라고 생각한 것은 트럼프라는 객체하나로 단순화(추상화)를 함.
그룹화(하트,클로버 병사 등을 트럼프라 묶고 토끼는 따로 묶으면 세상은 토끼와 트럼프 두개로 보임)
이렇게 그룹화(트럼프는 병사들의 차이점을 무시하고 공통점을 강조)를 하고나면 분류가 가능하고 분류를 하는 개념은 공통점을 기반으로 객체를 분류하는 체이고 이에 들어가있는 객체를 해당 개념의 인스턴스
라한다.
여기서 개념을 세가지 관점으로 이야기한다.
- 심볼(개념을 가르키는 이름)
- 내연(intention, 개념의 완전한 정의)
- 외연(extention, 객체들의 집합)
분류(중요한 개념 중 하나)란 객체에 특정한 개념을 적용하는 작업이고, 소프트웨어의 품질(잘 못하면 유지보수와 대처가 힘듦)을 결정한다. 더 중요한것은 개발자의 머릿속에 객체를 쉽게 찾고 조작 할 수있는 정신적 지도를 제공하고, 추상화를 위한 도구로 추상화의 두가지 개념을 모두 적용시킨다.
타입(개념과 동일함, 타입에 속하는 객체 int는 타입의 인스턴스이다)
타입은 데이터가 어떻게 사용되느냐(연산자)와 데이터를 메모리에 어떻게 표현하는지는 외부로부터 감춰짐.
실제로 우리는 객체를 일종의 데이터처럼 사용한다. 따라서 객체를 타입에 따라 분류하고 그 타입에 이름을 붙이는 것은 새로운 데이터 타입을 선언하는것과 같다. 하지만 객체는 행동(협력을 위해 책임을 지니는것)이 우선이다. 즉, 객체가 어떤 행동을 하느냐에 따라 객체의 타입이 결정된다. 그리고 객체는 내부가 감춰지는데, 이로써 같은 타입에 속한 객체는 행동만 동일하면 다른 데이터를 가질 수 있고, 행동이 동일하면 책임 또한 동일하다는 의미이다. 다만, 행동은 같으나 내부의 데이터가 다르므로 이것이 다형성을 의미
한다.
캡슐화 훌륭한 객체지향설계는 외부에 행동만을 제공하고 데이터는 뒤로 감춰야한다.
객체가 어떤 행동에 하느냐에 따라 객체의 타입이 결정 되기 때문에 데이터를 결정하고 객체의 책임을 결정하는 방법은 유연하지 못하다는 악몽을 초래해 책임-주도 설계
가 데이터-주도 설계의 단점을 보완하기 위해 나온것이다.
일반화\/특수화 관계
트럼프는 납작 엎드릴 수 있고, 트럼프 인간은 납작엎드리고 걸어다닐 수있다.
외연이라는 객체 집합의 관점에서 트럼프인간 타입의 외연은 트럼프 타입의 외연의 부분 집합
이다.(즉, 트럼프는 트럼프 인간을 포괄하는 좀 더 일반적인 개념이다) 그리고 트럼프인간은 트럼프보다 특화된 행동을 하는 특수한 개념
이다.
이것은 동시에 일어나고 객체지향에서 보면 데이터가 아닌 행동으로 본고, 트럼프는 트럼프인간의 일반화, 트럼프 인간은 트럼프의 특수화라 하고 각각 슈퍼타입과 서브타입이라고한다.(일반화해서 보면 단순하게 보여 일반화는 추상화를 위한 도구)
타입의 목적은 시간에 따라 동적으로 변하는 상태를 시간과 무관한 정적인 모습으로보기 위해서인데, 복잡성을 부과하던 시간을 제거하여 정적인 모습으로 일반화를 하는 것이다.
그래서 결국 타입은 추상화이다.
객체가 특정시점에 구체적으로 어떤 상태를 가지느냐를 스냅샷(이는 객체 다이어그램이라고도 함)이라 부른다. 스냅샷처럼 객체가 움직이는 동안 상태가 변하고 행동하는 지를 포착하는 것을 동적 모델
이라하고, 시간에 독립적인 모델을 타입 모델 또는 정적 모델
이라고도 함.
객체지향 프로그래밍을 할때 코드를 작성하는것은 정적인 관점에서 접근하고 디버깅할때는 동적인 관점에서 접근하는 것으로 우리는 두 관점을 모두 다뤄야 쉽게 이해가 가능하다.
클래스
객체지향에서 타입을 구현하는 가장 보편적인 방법은 클래스를 이용하는것인데, 클래스와 타입은 동일하지 않다. 클래스는 타입을 구현할 수있는구현매커니즘 중 하나이고, 객체를 분류하는 기준은 타입이며 이를 나누는 기준은 객체의 행동이다.
결국 객체지향에서 중요한 것은 상태와 행위이다.
그래서 추상화와 일반화가 다른가??? 엄밀히 말하자면 둘은 다른 단어이지만 일맥상통한다고 보면 된다. (참고 : 포프머신[https://blog.popekim.com/ko/2022/07/19/generalization-vs-abstraction.html])
04. 역할,책임,협력
요청은 책임을 암시한다.
제일 중요한 능력은 책임을 능숙하게 소프트웨어 객체에 할당하는 것이다. - 크레이그 라만 객체와 책임이 부유하는 상황에서 성급하게 구현하는 것은 협력에 참여가 불가능한 비자율적인 객체를 낳는다.
책임은 무엇을알고있는가(스스로 하는 것, 다른 객체의 행동을 시작 시키는것, 다른 객체의 활동을 제어하고 조절하는 것)와 무엇을 할 수있는가(개인적인 정보, 관련된 객체에 관해, 자신이 유도하거나 계산 가능한것에 대해 아는것)로 나뉜다.
메시지를 기반으로 객체 간의 상호 협력이 가능해진다. 다만, 책임과 메시지의 수준은 같지 않다.
책임은 객체가 협력에 참여하기 위해 수행해야 하는 행위를 상위 수준에서 개략적으로 서술한 것
이고, 책임을 결정한 후 협력을 정제하면서 이를 메시지로 변환할 때는 하나의 책임이 여러 메시지로 분할되는 것이 일반적이다.(초기에는 개요만 아는것으로 충분)
역할을 이용해 협력을 추상화
(판사라는 직책을 왕이나 여왕이 맡을 수 있는것처럼), 일반화\/특수화의 관점에서는 역할은 일반화이며 객체의 타입은 특수화이다. 역할이 협력을 추상화 할 수 있는 이유는 역할 자체가 객체의 추상화이기때문이다.
객체의 행위에 초점을 맞추기 위해 협력이라는 실행 문맥안에서 책임을 분배해라.
책임-주도설계
는 협력관계를 고안하며 이에 필요한 역할과 책임을 식별 후 분류해나가는 과정이고 여기서 역할, 책임, 협력에 집중하라. 디자인패턴
은 앞서 말한 방법이 객체의 역할,책임,협력을 고안하기 위한 방법과 절차라면 책임-주도설계의 결과를 표현이고, 반복적으로 발생하는 패턴에 대해 정리해놓은것이다. 특정한 상황에 적용가능한 디자인 패턴을 잘 알고있다면 책임-주도 설계의 절차를 손쉽게 포착가능하다.(즉, 책임-주도설계의 결과물인 동시에 지름길이다.) 테스트-주도 개발
은 애자일 방법 중 XP로부터 주목받기 시작한 설계기법이다. 테스트를 통과하는 가장 간단한 코드를 작성한 후 리팩터링을 통해 중복을 제거하는 것 방법이다.(책임-주도설계의 기본 개념과 다양한 원칙 등을 종합적으로 이해하고 좋은 설계 경험을 길러야만 적용가능한 설계기법).
05. 책임과 메시지
책임과 분산의 효과를 통한 책임의 중욕성
재판과정을 예를 들면 ‘증언하라’라는 말은 증인이 증언하는 방식(문서제출, 구두서술)등을 제한하지 않지만 ‘목격했던 장면을 떠올려라’라는 증언의 방식은 증인의 증언방식을 크게 제한한다. (즉, 객체지향세계에서는 자율적인 객체들의 공동체이므로 객체가 자율적이기 위해서는 객체에 할당되는 책임의 수준역시 자율적이어야한다)
자율적인 책임의 특징은 어떻게가 아닌가 무엇을 해야하는가를 설명한다.
메시지(메시지 이름과, 인자로 구성, 증언하라(어제, 왕국)), 메서드(메시지를 처리할 방법) 다형성(동일한 객체들 사이의 대체 가능성을 의미, 이는 설계를 유연하고 재사용 가능하게 만드는 수신자의 종류를 캡슐화하는 것)
객체-지향 시스템은 객체들의 연결망(Web)이다.
클래스가 코드를 구현하기 위해 사용할수 있는 추상화 도구이지만 객체지향은 클래스가 아니라 객체들이 주고받는 메시지로부터나온다.
훌륭한 객체지향 설계란 어떤 객체가 어떤 메시지를 전송할 수 있는가와 어떤 객체가 어떤 메시지를 이해할 수 있는가를 중심으로 객체사이의 협력관계를 구성하는것이다. 개별 객체에 초점을 맞추는 관점과 매우 다르다.
객체가 책임을 완수하기 위해 다른 객체의 도움이 필요하다고 판단되면 어떤 메시지가 필요한지 결정하고 그에 따른 적합한 객체를 선택 결과적으로 메시지가 수신자의 책임을 결정한다. 즉, 메시지의 객체의 행위를 결정하고 그 행위를 어떤 객체가 수행할지를 정하는 방법을 What/Who 사이클
이라한다.
메시지를 결정하고 그에 맞게 객체를 결정하는 방식은 묻지말고시켜라(Tell, Don't ask)
인데 이 방식은 메시지가 어떻게 하는지 지시하지말고 무엇을 해야하는지 요청하는 것이라고 설명하는 것이고 이 전환은 객체에 의존하는 부분을 줄이고 결합도가 낮아져 유연해진다.
메시지를 믿으면 자율적인 책임은 저절로 따라온다.
객체지향의 힘은 대부분 객체의 외부와 내부를 구분하는 것에서 나온다
인터페이스를 어떤 두 사물이 마주치는 경계지점에서 서로 상호작용할수 있게 이어주는 방법이나 장치라 부른다.
인터페이스는 사용법을 익히기만 하면 내부 구조나 동작방식을 몰라도 쉽게 대상을 조작하거나 의사 전달이 가능하고, 인터페이스 자체를 변경하지 않고 단순히 내부 구성을 변경하는것은 사용자에게 어떤 영향도 미치지 않는다. 그리고 대상이 변하더라도 동일한 인터페이스를 제공하면 아무런 문제없이 상호작용한다. 객체가 서로 상호작용하는 유일한 방법은 메시지 전송이며 따라서 인터페이스는 메시지의 목록으로 구성해 객체가 제공하는 인터페이스의 모양을 가진다.
외부에 공개된 인터페이스를 공용인터페이스라 부른다.
객체관점에서 생각하는법 첫째, 추상적인 인터페이스(자율성보장을 위해 세부사항제거) 둘째, 최소 인터페이스(외부 노출 최소, 내부 수정시 영향 최소화) 셋째, 인터페이스와 구현(내부 구조와 작동박식을 가리키는 고유용어)간의 차이
소프트웨어는 항상 변하기때문에 외부와 내부를 분리할 필요가 있다.
객체의 자율성을 위해 구현을 외부로 부터 감추는 것(정보은닉)을 캡슐화라 한다.
상태와 행위의 캡슐화 | 객체는 상태와 행위의 조합이다. 그래서 객체는 스스로 자신의 상태를 관리하며 상태를 변경하고 외부에 응답 할 수 있는 행동을 내부에 함께 보관하는데 이를 데이터 캡슐화라한다. |
사적인비밀의 캡슐화 | 공용인터페이스로만 의사소통 할 수 있는 경로고 나머지 불안정한 비밀은 구현과 관련된 세부사항으로 안정적인 인터페이스 뒤로 숨긴다. |
객체를 자율적인 존재로 바라보는 것은 객체의 내부와 외부를 엄격하게 분리하는것이다. 또한 객체지향은 이처럼 명확한 구분을 하는 객체들로 구성된 협력공동체이다.
책임을 결정하는 것은 메시지이고 자율적인 책임의 특성은 책임을 수행하게하는 메시지의 특성과 연결이 된다.
여기서 책임의 자율성은 협력의 품질을 결정하는데, 자율적인 책임은 협력을 단순하게 만들고, 외부와 내부를 명확히 분리하며 책임을 수행하는 내부적인 방법을 변경해도 외부에 영향을 미치지 않는다. 또한, 책임이 자율적일수록 협력의 대상을 다양하게 선택할 수 있는 유연성을 제공한다.(즉, 설계가 유연해지고 재사용성이 높아지며 객체의 역할을 이해하기 쉬워짐)
이를 다시 말하자면 책임이 자율적일수록 적절하게 추상화되며 응집도가 높아지고 결합도가 낮아지며 캠슐화가 증진되고 인터페이스와 구현이 명확히 분리되며 설계의 유연성과 재사용성이 향상되는 것이다.
객체지향의 강력함은 책임을 자율적으로 만드는 것, 이는 메시지에 따라 달라진다.
객체란 기계와 같다. 블랙박스 처럼 객체에 제공하는 버튼을 통해서만 상태에 접근할 수있는 점이 캡슐화를 강조하고, 객체가 제공하는 버튼을 통해서만 동작이 가능하고 그안에서 돌아가는 것은 객체의 자율적인 특성이다.
06. 객체 지도
유일하게 변하지 않는 것은 모든것이 변한다는 사실뿐이다 - 헤라클레이토스
길을 물어보는 첫번째 방법은 기능적이고 해결지향적인 방법, 두번째는 구조적이고 문제지향적인 접근이다.
첫번째는 큰 바위에서 왼쪽으로 가시오. 등과 같은 안내이고, 두번째는 지도로 비유되는데 이는 기능이 아닌 구조를 기반으로 모델을 구축하는 편이 좀 더 범용적이고 이해하기 쉬우며 변경에 안정적이라는 것이다. 이번에는 기능이 아니라 구조를 바탕으로 시스템을 분할하는 객체지향의 또 다른 측면에 관해 설명한다.
자주 변경되는 기능이 아니라 안정적인 구조를 따라 역할, 책임, 협력을 구성해라.
안정적인 구조를 기반으로 시스템을 분할하는 객체지향적인 접근법은 역할, 책임, 협력을 기반으로 시스템의 기능을 구현하는 책임-주도설계의 본질을 이해하는데 도움이 될것이다. 훌륭한 기능이 휼륭한 소프트웨어를 만드는 충분 조건이라 한다면 훌륭한 구조는 훌륭한 소프트웨어를 만들기 위한 필요조건이다.
요구사항은 변경되고 요구사항을 만족하는 다양한 설계안들을 저울질하면서 그 결고ㅘ로 단순화면서도 유연한 설계를 창조하는것은 공학이라기보다는 예술에 가깝다. 워런 웨이거의 말처럼 우리는 미래에 대해 얘기할수 있고 놀수있고 추측할수있고 깊이 생각해 볼 수 있으며 이론과 모형을 구축하고 이에 영향을 미치는 정량 데이터를 수집 할 수 있지만 미래를 전혀 알지못한다. 우리는 미래를 예측할수없다. 단지 대비할수있을 뿐이고 이에 대비하는 가장좋은방법은 예측이 아닌 수용이다.
지도 은유를 통해 수용에 가장 좋은 방법은 안정적인 구조를 설계하는것이다. 객체지향 접근방법은 자주 변경되지않는 안정적인 객체 구조를 바탕으로 객체간의 책임을 분배하고 객체의 구조에 집중해 기능이 객체의 구조를 따르게 만든다.
이것이 객체를 기반으로 책임과 역할을 식별하고 메시지를 기반으로 객체들의 협력관계를 구축하는 이유이다.
기능 | 사용자의 목표를 만족시키기 위해 책임을 수행하는 시스템의 행위 |
구조 | 이해관계자들이 도메인에 관해 생각하는 개념과 개념들 간의 관계로 표현 |
모델 | 지식을 선택적으로 단순화 하고 의식적으로 구조화한 형태 |
도메인 | 사용자가 프로그램을 사용하는 대상 분야 |
기능을 수집하고 표현하는 기법을 유스케이스 모델링
이라 하고, 구조를 수집하고 표현하기 위한 기법을 도메인 모델링
이라 한다.
각각의 결과를 유스케이스와 도메인 모델이라 한다.
여기서 도메이닌 모델은 사용자가 프로그램을 사용하는 대상 영역에 관한 지식을 선택적으로 단순화하고 의식적으로 구조화한 형태이며, 소프트웨어가 목적으로하는 영역내의 개념과 관계, 규칙이나 제약등을 주의깊게 추상화한것이다. 이는 현상을 이해하고 반응하기 위해 자신의 마음속에 멘탈 모델을 구축하고 제품 모델을 설계할때 제품에 관한 모든것이 사용자들의 멘탈모델과 일치해야한다고 함. 결론적으로 객체지향을 이용하면 도메인에 대한 사용자 모델, 디자인 모델, 시스템 이미지 모두 유지하는것이 가능하고 이를 연결완정성 또는 표현적 차이
라고 한다.(객체는 현실객체에 대한 추상화가 아니고, 가장 효과적으로 표현하는 것은 은유)
사용자의 목표가 유스케이스의 핵심이다.
기능(기능적 요구사항) | 사용자에게 제공해야하는 기능의 목록을 정리한 것 |
일차엑터 | 하나의 목표를가지고 유스케이스를 시작하는 엑터를 의미 |
불안정한 기능을 담는 안정적인 도메인 모델이고, 불안정한 재료로 기능인 유스케이스는 사용자의 목표를 달성하기위해 사용자와 시스템간에 이뤄지는 상호작용의 흐름을 텍스트로 정리한 것이다.
유스케이스는 여러 시나리오(특정한 이야기 또는 경로, 유스케이스 인스턴스)들의 집합이다. 단순한 기능(피처)의 목록과 다르다. 단순히 기능을 나열하는 것이 아닌 이야기를 통해 연관된 기능들을 함께 묶을 수있다는 점과 사용자 인터페이스와 관련된 세부정보(내부 설계와 관련된 정보)를 포함하지 말아야 한다. 이러한 사용자 인터페이스를 배제한 유스케이스를 본질적인 유스케이스라한다.
유스케이스는 설계기법도, 객체지향기법도 아닌 사용자가 시스템을 통해 무엇을 얻을 수 있고 어떻게 상호작용하느냐에 대한 정보만 기술되어있다.
재료합치기(도메인모델,유스케이스,책임-주도설계)
안정적인 구조는 도메인모델로 불안정한 구조는 유스케이스를 활용한다. 그리고 책임-주도 설계에서는 앞 내용부터 지금까지는 시스템이 사용자에게 제공할 기능이 있다는 가정하에 객체들간의 협력을 설계했지만 사실 협력의 출발은 시스템의 기능을 시스템의 책임으로 바꾼 후 얻어지는 것이다.
이렇게 시스템에 할당된 책임은 작은 규모의 객체들이 수행하는 더 작은 책임으로 세분화해야 한다. 그렇다면 ‘어떤 객체를 할당할 것인가?’ 여기서 도메인 모델이 등장하며 도메인 모델에 포함된 개념을 은유하는 객체를 선택해야하고 이는 소프트웨어와 코드 사이의 표현적 차이를 줄이는 첫 걸음
이다. 이렇게 코드는 불안정한 기능을 수용할 수 있는 안정적인 구조에 기반한다. 유스케이스는 사용자에게 제공해야할 기능을 시스템의 책임으로 보게함으로써 객체간의 안정적인 구조에 책임을 분배할수있는 출발점을 제공
한다. 책임-주도설계는 유스케이스로부터 첫번째 메시지와 사용자가 달성하려는 목표를 도메인모델로부터 안정적인 구조를 제공받아 실제 동작하는 객체들의 협력공동체를 창조한다. 책임-주도설계는 시스템의 기능을 역할과 책임을 수행하는 객체들의 협력관계로 바라봄으로써 유스케이스와 도메인 모델을 통합
한다. 여기서 사용자의 관점에서 시스템을 명시하고 사용자와 설계자가 공유하는 안정적인 구조를 기반으로 기능을 책임으로 변환하는 체계적인 절차를 따라야한다는것이 중요하다.
도메인 모델은 문서나 다이어그램이 아니라 멘탈 모델이고 코드로부터 도메인모델을 유추할수있게하는것이 진정한 목표다.
안정적인 도메인모델을 기반으로 시스템의 기능을 구현하라 도메인 모델과 코드를 밀접하게 연관시키기위해 노력하면 그것이 유지보수하기 쉽고 유연한 객체지향 시스템을 만드는 첫걸음이 될것이다.
07. 함께 모으기
코드와 모델을 밀접하게 연관시키는것은 코드에 의미를 부여하고 모델을 적절하게 한다.
마틴 파울러는 객체지향설계 안에 존재하는 세가지 상호 연관된 관점으로 바라보야아 한다고 했다.
개념관점 | 개념과 개념사이의 관계(사용자가 도메인을 바라보는관계) |
명세관점 | 객체들의 책임(협력을 위해 무엇을 할 수있는가) |
구현관점 | 책임을 수행하는데 필요한 동작하는 코드를 작성(객체의 책임을 어떻게 수행할것인가에 초점) |
커피전문점을 예시로 들어보자.
커피전문점이라는 도메인안에서 손님, 바리스타, 메뉴판이라는 타입을 지정하고 메뉴판안에있는 커피들은 모두 객체이다. 타입은 분류를 위한 것으로 손님타입, 바리스타타입, 메뉴판 타입, 메뉴항목타입 등이고, 손님1이라는 객체는 손님타입의 인스턴스로 바라본다. 또한, 메뉴항목타입이 메뉴판 타입에 포함 관계 또는 합성관계이고, 손님은 메뉴판을 알고 있어야하기 때문에 이들 사이에는 연관관계라고 한다. 이러한 부분을 도메인 모델이라한다.
객체지향설계의 첫번째 목표는 훌륭한 협력을 설계하는것이다. 현재 설계하고 있는 협력은 커피를 주문하는 것.
이것은 커피를 주문하라는 메시지
이고 그 위에 부가적인 정보인 인자의 형식으로 구현한다. 메시지를 찾았으니 적합한 객체를 선택
해야한다 우리는 어떤 현실 객체를 은유 해야하는 가, 커피 전문점이라는 도메인 모델에서 책임을 수행하기에 적절한 타입을 발견하고 책임을 수행할 객체를 그 타입의 인스턴스로 만들어라.
위에 ‘메시지를 수신할객체는 무엇인가?’ 이는 어떤 객체가 커피를 주문할 책임을 져야하지이고, 해당하는 객체는 손님타입의 인스턴스이다. 현실세계에서 메뉴판은 손님이 펼치고 닫는 수동적인 객체이지만 객체지향세계에서는 모든 객체가 능동적이며 자율적이라 이 세계에서의 메뉴판은 스스로 메뉴항목을 찾는디.
이는 우리가 무생물을 의인화 해야하는 것이다.
이러한 부분이 우리가 현실속의 객체를 모방하거나 추상화하는 것이 아닌 은유하는 이유
이다. 협력을 가시화 하기위해 꼭 UML을 사용할 필요는 없다. 의사소통이라는 목적에 부합하면 얼마든지 수정하고 뒤틀어라(UML은 의사소통을 위한 표기법이지 꼭 지켜야하는 법칙은 아니다)
이렇게 위에서 얻어낸것은 객체들의 인터페이스이다.
객체가 수신한 메시지가 인터페이스를 결정한다는 사실을 기억해라. 앞의 설계작업은 구현단계를 거치면서 대부분 변경된다. 설계작업은 구현을 위한 스케치이지 구현 그 자체 일 수는 없다.
중요한것은 설계가 아닌 코드이다. 따라서 설계에 너무 오랜시간을 쏟지말고 최대한 빨리 구현해서 설계의 이상이나 구현가능한지여부를 판단해야한다. 코드를 통한 피드백없이는 깔끔한 설계가 불가능하다. 설계가 제대로 그려지지 않는다면 실제 코드를 작성해가며 협력의 전체적인 밑 그림을 그려라. 테스트-주도설계로 코드를 구현하는 사람들이 하는 작업이 바로 이것이다. 그들은 테스트 코드를 작성해가면서 협력을 설계한다.
코드는 세가지 관점을 모두 제공해야한다. 개념관점에서는 커피 전문점 도메인을 구성하는 중요한 개념과 관계를 반영한다는 사실을 쉽게 알 수 있다. 소프트웨어와 도메인 클래스 사이의 간격이 좁으면 좁을 수록 기능을 변경하기 위해 뒤적거려야하는 코드의 양도 점점 줄어든다. 명세관점은 클래스의 인터페이스를 바라보고 세부사항이 들어나지않게해야한다. 변화에 탄력적인 인터페이스를 만들 수 있는 능력은 객체지향설계의 수준을 가늠하는 중요한 척도이다. 구현관점은 클래스의 내부 구현을 바라보고 메서드와 속성은 구현에 속하며 공용인터페이스의 일부가 아니다.
이러한 세가지 관점이 명확하게 드러나는 코드로 개선하면 변경에 유연하게 대응할수있는 객체지향코드를 작성하는 빠른길이다.
또한, 인터페이스와 구현을 분리하라. 명세와 구현관점이 뒤섞여 머릿속을 함부로 어지럽히지 못하게 막아야한다. 명세는 클래스의 안정적인 측명을 드러내야하고 구현관점은 클래스의 불안정한 측면을 드러내야한다. 중요한것은 클래스를 봤을때 클래스를 명세와 구현관점으로 나눠 볼 수 있어야 하는것이다.
08. 부록 | 추상화 기법
추상화는 도메인의 복잡성을 단순화 하고 직관적인 멘탈모델을 만드는데 사용할수있는 가장 기본적인 인지수단
분류와 인스턴스화
객체를 분류하고 범주로 묶는 것은 객체들의 특정집합에 공통의 개념을 적용하는것을 의미한다. 객체에 존재하는 객체에 개념을 적용하는 과정을 분류라 하고, 수 많은 개별적현상을 객체, 하나의 개념을 타입이라한다.
타입과 집합 타입은 심볼, 내연, 외연이고 집합은 외연을 가리키는 또 다른 명칭이다.
객체들은 동시에 서로 다른 집합에 포함이 가능한데, 객체지향 프로그래밍 언어들은 단일분류만 지원한다. 여기서 객체가 한 집합에서 다른 집합의 원소로 자신이 속하는 타입을 변경하는 경우를 동적 분류라하고 불가능한경우를 정적분류라한다. 프로그래밍언어에서는 정적 분류만 제공하고 다중분류와 동적 분류는 개념적인 관점에서 도메인을 분석하는데 유연 하지만 구현으로 옮기기 쉽지 않는다. 이는 도메인 모델의 초안을 만든 후 구현에 적합하도록 재조정하는 과정이 분석과 구현간의 차이를 메우는 가장현실적인 방법이다. 타입을 구현하는 가 장보편적인 방법은 클래스를 활용하는 것인데, 클래스와 타입은 동일한 개념이 아니기 때문에 클래스기반의 객체지향언어는 아리스토텔레스의 철학을 기반으로 본질(Essence)을 한 사물의 가장 핵심적인 필수 불가결한 속성을 정의한다. 에를 들어 어떤 사람이 회사원이 되었다고해도 사람이라는 본질을 잃지 않는것처럼 우연적 속성(본질적이지 않은)이 아니라 말이다. 대부분 객체지향에서는 우연적인 속성을 구현하지는 못한다.
일반화와 특수화
계층구조에서 상위 계층에 위치한 범주를 하위에 위차한 범주의 일반화라하고 하위에 위치한 범주는 계층의 상위에 위치한 범주의 특수화라한다. 어떤 타입이 다른 타입보다 일반적이라면 슈퍼타입, 좀 더 특수하다면 서브타입이라한다.
어떤타입이 다른 타입의 서브타입이 되려면, 백퍼센트 규칙(슈퍼타입의 정의가 서브타입에 백퍼센트 적용)과 이즈-에이 규칙을 준수해야한다. 이 규칙은 타입의 외연과 관련된 규칙(서브타입의 모든 인스턴스는 슈퍼타입 집합에 포함 돼야한다, 서브타입 is a 슈퍼타입)이다.
프로그램잉 언어를 이용해 일반화와 특수화를 구현하는 방법은 상속을 사용하는것인데 모든 상속관계가 일반화 관계는 아니다. 일반화 원칙은 한 타입이 다른 타입의 서브타입이 되기 위해서는 슈퍼타입에 순응해야하는데 순응에는 구조적순응(속성과 연관 관계에 관한것, 내연과 관련된 백퍼센트 규칙)과 행위적 순응(행위가 동일한 계약을 기반으로 하느냐로 타입의 행위에 관한것, 리스코프 치환원칙으로 서브타입은 슈퍼타입을 행위적으로 대체 가능 해야함)이 두가지있다. 상속은 서브타이핑(서브클래스가 슈퍼클래스를 대체할수있는경우, 인터페이스 상속)과 서브 클래싱(서브클래스가 슈퍼클래스를 대체할수없는경우, 구현상속) 용도로 사용이 된다.
집합과 분해
시계에 관한 호라와 템프스의 우화에서 복잡성은 계층의 형태를 띈다는 것과 단순한 형태에서 복잡한 형태로 진화하는데 걸리는 시간은 그 사이에 존재하는 안정적인 형태의 수와 분포에 의존한다
라는 점을 배울 수 있다.
이처럼 안정적인 형태의 부분으로 전체를 구축하는 행위를 집합이라하고 반대를 분해라한다. 전체와 부분간의 일관된 계층구조는 재귀적인 설계를 가능하게한다. 집합은 전체의 내부로 불필요한 세부사항을 감추기에 추상화 매커니즘인 동시에 캠슐화 매커니즘이다.
합성관계는 객체와 객체 사이의 전체-부분관계를 구현하기위해 사용되고, 부분을 전체 안에 캡슐화함으로써 인지 과부하를 방지한다. 이 관계를 이용해 계층적인 객체들의 그룹을 만드는것은 그룹과 관련된 복잡성을 완화 하기 위해서이다.
또한, 주문과 주문항목은 합성관계인것에 반해 주문과 상품은 단순한 물리적 통로가 존재 한다는 사실만을 나타낸것을 연관관계라 한다. 그리고 서로 연관성이 높은 클래스 집합을 논리적인 단위로 통합하는 구성요소를 패키지 또는 모듈이라한다.
객체지향 사고 프로세스 5판
1. 객체지향 프로그래밍과 절차적 프로그래밍과의 차이점
객체지향설계에서 속성과 행위는 한가지 객체(블랙박스)에 모두 포함되는 반면 절차적 설계나 구조적설계에서는 속성과 행위가 일반적으로 분리된다.
객체지향과 구조적 개발은 공존한다.
절차적 프로그래밍은 데이터와 연산을 분리하지만, 객체지향프로그래밍은 데이터와 연산이 캡슐화한다.
객체지향 - 캡슐화, 상속, 다형성, 합성, 추상화
2. 객체라는 관점에서 생각하는 방법
설계과정초기에는 업무에 관련된 문제를 식별하고 해결해야 한다.
(즉, 개념을 분석 하고 설계하는 일) 예를 들어 무선 통신망을 구축하기 전에 무선 기술이 없으면 안되는 것과 같이 ‘무선’이라는 개념을 알아야한다.
세가지 중요 첫째, 인터페이스와 구현부(implementation)의 차이를 이해 - 인터페이스(사용자가 알아야 할 내용만), 구현부(드러나지 않고, 구현부를 변경해도 사용자는 변경하지 않아도 되는것) 둘째, 가능한 사용자 인터페이스를 적게 제공할 수 있게 더 추상적으로 생각(‘왼쪽으로 갔다가 오른쪽으로 가주세요’ 말고 ‘공항으로 가주세요’ 처럼) 셋째, 가능한 한 인터페이스를 적게 제공
대부분의 개발자는 코드를 철저히 문서화해야 한다는 점을 알고 있지만 일반적으로 시간을 내어하고 싶어하지 않는다. 그러나 문서화를 잘하는 좋은 습관이 없이는 훌륭하게 설계하기는 불가능하다.
설계시 고려할 점으로 재사용을 고려한 설계, 확장성을 고려한 설계, 범위를 가장 작게 설계이다.
대부분의 설계 및 프로그래밍함수와 마찬가지로 반복적인 과정을 겪는게 바람직하다. 이것이 의미하는 바는 모든 코드를 한번에 작성하지 말아야 한다는 개념과 코드 크기를 작게해 각 단계별 빌드 및 테스트를 진행해야한다는 것이다.
스텁(인터페이스를 최소한으로 구현한 것)은 실제 코드 작성하지 않고 인터페이스를 테스트하는 것, 완성후 삭제하지말고 코드안에 두자.
일반적으로 견고한 객체지향 설계과정
적절한 분석 - 시스템 설명작업명세서 개발 - 요구사항수집 - 인터페이스용 프로토타입 - 클래스 식별 - 각 클래스 역할결정 - 상호작용방식 결정 - 시스템을 설명하는 고급 모델 구성
즉, 프로젝트의 기본이 되는 코드를 설계할때 어느 부분이 필요한지 등의 개념을 분석하고 설계하는 단계부터 거쳐야함.
래퍼(wrappers)
구조화된 코드(for while if 문등과 같은 구문) 감싸기 이식하기 어려운 코드(네이티브 코드) 감싸기 기존 클래스 둘러싸기(클래스 자체를 둘러싸야할 필요성은 명백하지 않을 수 있으니 종종 다른 사람이 작성한코드를 사용하는 경우 등)
3. 재사용 매커니즘
행위는 주로 구현하고 데이터,모델은 상속받는 경향. 그럼에도 둘다 재사용 매커니즘
상속과 합성은 둘다 객체지향 시스템 구축에 중요한 기술(일반화-특수화 구조)이다.
상속은 is-a(b는 a)를 지닌다. 예를 들면, 개와 리트리버면 리트리버 b는 개a이다. 합성은 has a 지닌다. 예를 들면, 자동차는 운전대가 있고 차는 엔진 문 스트레오 등으로 구성되어있다.
캡슐화는 상속과 무슨관계인가? 캡슐화는 객체지향 기본 규칙 중 하나이며 상속은 주요 개념 중 하나로 간주하는데, 캡슐화를 약화시키는 방법으로 상속이 그 밖의 클래스는 강력하게 캡슐화가 되나 정작 슈퍼와 서브 클래스 사이에는 캡슐화가 약해진다.
기본적으로 합성을 사용하며 필요할때 상속을 사용하는 건 단순해 보인다는 생각이다. 합성과 상속을 적절히 사용해야함
4. 프레임워크
재사용가능한 코드개발을 용이하게 하기위한 매커니즘 중 한가지는 프레임워크(표준화, 플러그 앤 플레이,API 어플리케이션 프로그래밍 인터페이스)를 작성
추상 클래스 | 구현부가 없는 메서드가 한 개 이상들어있는 클래스 예를 들면, shape라는 클래스에 있는 rectangle, circle 클래스들이 각각 draw라는 메서드를 가지고있으면 각 draw는 다른 응답이 발생하는데 여기서 shape를 추상 클래스라 한다.
만약 구조적 프로그래밍 방식이라면? 흥미로운 문제로 자바의 경우 switch문과 같은 형태로 지저분해 질 수 있음.(이것은 객체지향 설계의 장점)
계약형태로 설계함으로써 코드를 재사용하는 방법(각 코드들이 중구난방으로 버전이 올라가게 된다면, 우리는 엄청난 손실을 받는다. 이러한문제를 해결하기위해)
3. 결론
나는 이제 객체지향 세계에 발을 들이밀게 되었다.
결론적으로는 ‘객체지향의 사실과 오해’ 도서에서 마지막 07. 함께 모으기
부분의 글을 이해하면서 객체지향에 대해 조금은 알게 된 것 같았다. ‘객체지향의 사실과 오해’ 도서가 개인적으로 이해하기 수월하기도 했고, 두 도서의 내용은 매우 비슷해 겹치는 내용들이 많아 중복되는 내용들은 더 적지 않았었다.
그리고 객체지향에 관해서 조언으로 아래의 얘기를 들었는데,
객체지향 자체가 철학적으로 접근해야하고 좋은 코드를 많이 보고 분해해 봐야안다. 좋은 객체지향 코드란 남들이 편히 쓸 수 있는것이다.
‘객체지향 사고 프로세스’를 ‘객체지향의 사실과 오해’보다 먼저 읽었었는데, 처음 접할때 뜬 구름잡는 것처럼 느껴졌다. 앞으로 나중에는 좋은 코드들에 대해 많이 접하고 작성한 뒤에 ‘객체지향 사고 프로세스’를 다시 읽어 구름을 내려다 봐야겠다.
두 도서를 통해 많은 내용을 배우게 되어 도움이 많이 되었고 객체지향 프로그래밍 언어를 다룬다고 하면 꼭 한번은 읽어야 하는 도서가 아닌가 싶다.