유일하게 변하지 않는 것은 모든 것이 변한다는 사실뿐이다.
- 헤라클레이토스(Heraclitus of Ephesus)
여행 중에 다른 마을로 이동해야 하는데 길을 모른다고 가정해 보자. 이 경우 사람들은 다음의 두 가지 방법 중 하나를 이용해 길을 찾는다.
1. 지나가는 사람에게 마을까지 가는 길을 직접 물어본다
2. 지도에 표시된 길을 따라가는 방법
길을 찾는 입장에서는 어떤 방법을 사용하건 상관없이 목적지로 이동할 수 있겠지만 방법에 따라 길을 찾는 과정과 난이도면에서는 차이가 난다.
첫 번째 방법은 '기능적이고 해결책 지향적인 접근법(functional, solution-directed approach)'이다. 길을 가르쳐 주는 사람은 다른 마을까지 가는 경로를 단계별로 상세히 설명해야 한다. 사람들이 올바른 길을 알려주고 지시를 올바르게 따른다면 원하는 마을로 이동할 수 있을 것이다. 그러나 이 방법은 일반적이지도, 재사용 가능하지도 않다. 강이나 산과 같은 중요한 랜드마크가 없다면 경로를 설명하기 어려울 뿐만 아니라, 설명만으로 경로를 찾기도 쉽지 않다.
두 번째 방법은 '구조적이고 문제 지향적인 접근법(structural, problem-directed approach)'이다. 지도는 길을 찾는 데 필요한 구체적인 기능이 아니라 길을 찾을 수 있는 '구조'를 제공한다. 지도는 길을 찾는 데 필요한 주변 지형을 추상적으로 표현하고 있기 때문에 실세계의 환경과 우리의 지식을 밀접하게 연관 지을 수 있게 해준다. 지도는 안정적인 실세계 지형을 기반으로 한 추상 모델이다.
전통적인 소프트웨어 개발 방법은 변경이 빈번하게 발생하는 기능에 안정적인 구조를 종속시키는 길을 묻는 방법과 유사하다. 반면에 객체지향 개발 방법은 안정적인 구조에 변경이 빈번하게 발생하는 기능을 종속시키는 지도의 방법과 유사하다. 이것이 객체지향이 과거의 전통적인 방법보다 범용적이고, 재사용성이 높으며, 변경에 안정적인 이유다. 즉, 객체지향은 자주 변경되는 기능이 아니라 안정적인 구조를 기반으로 시스템을 구조화한다.
앞에서는 객체지향을 역할과 책임을 수행하며 협력하는 자율적인 객체들의 공동체로 정의했다.
이번 장에서는 기능이 아니라 구조를 바탕으로 시스템을 분할하는 객체지향의 또 다른 측면에 관해 설명한다. 자주 변경되는 기능이 아니라 안정적인 구조를 기반으로 시스템을 분할하는 객체지향적인 접근법은 역할, 책임, 협력을 기반으로 시스템의 기능을 구현하는 책임-주도 설계의 본질을 이해하는 데도 도움이 될 것이다.
자주 변경되는 기능이 아니라 안정적인 구조를 따라 역할, 책임, 협력을 구성하라. 이 것이 이번 장의 주제다.
기능 설계 대 구조 설계
모든 소프트웨어 제품의 설계에는 두 가지 측면이 존재한다. 하나는 '기능(function)' 측면의 설계이고, 다른 하나는 '구조(structure)' 측면의 설계다.
기능 측면의 설계는 제품이 사용자를 위해 무엇을 할 수 있는지에 초점을 맞춘다.
구조 측면의 설계는 제품의 형태가 어떠해야 하는지에 초점을 맞춘다.
설계의 가장 큰 도전은 기능과 구조라는 두 가지 측면을 함께 녹여 조화를 이루도록 만드는 것이다.
소프트웨어가 사용자에게 가치 있는 이유는 사용자가 필요로 하는 기능을 제공하기 때문이다. 이런 관점에서 소프트웨어를 개발하는 일차적인 이유는 사용자에게 훌륭한 기능을 제공하기 위해서다. 소프트웨어의 기능은 사용자가 금전적인 대가를 지불하고서라도 구매할 수 있을 정도로 매력적이어야 한다. 따라서 소프트웨어를 개발하는 초기 단계에서는 사용자가 무엇을 원하는지, 그리고 사용자가 원하는 것을 만족시키기 위해 시스템이 어떤 기능을 제공해야 하는지에 초점을 맞춰야 한다.
훌륭한 기능이 훌륭한 소프트웨어를 만드는 충분조건이라고 한다면 훌륭한 구조는 훌륭한 소프트웨어를 만들기 위한 필요조건이다. 성공적인 소프트웨어들이 지닌 공통적인 특징은 훌륭한 기능을 제공하는 동시에 사용자가 원하는 새로운 기능을 빠르고 안정적으로 추가할 수 있다는 것이다. 비록 최종 사용자들이 소프트웨어의 내부 구조를 볼 수는 없지만 깔끔하고 단순하며 유지보수하기 쉬운 설꼐는 사용자의 변하는 요구사항을 반영할 수 있도록 쉽게 확장 가능한 소프트웨어를 창조할 수 있는 기반이 된다.
요구사항이 변경되지 않는다면 개발자의 삶은 좀 더 단순하고 지루했을지도 모른다. 요구사항이 변경되지 않는다면 코드를 어떻게 작성하는지를 가지고 이렇게 골치를 썩을 필요도 없었을 것이다. 불행하게도 요구사항은 변경된다. 소프트웨어 분야에서 예외가 없는 유일한 규칙은 요구사항이 항상 변경된다는 것이다. 설계라는 행위를 중요하게 만드는 것은 변경에 대한 필요성이다. 안타깝게도 변경을 피할 수 잇는 방법은 없기 때문에 좋은 설계에 대한 압력 역시 피할 수 없다.
설계가 어려운 이유는 어제 약속했던 기능을 제공하는 동시에 내일 변경될지도 모르는 요구사항도 수용할 수 있는 코드를 창조해야 하기 때문이다. 미래에 대비하는 가장 좋은 방법은 변경을 예측하는 것이 아니라 변경을 수용할 수 있는 선택의 여지를 설계에 마련해 놓는 것이다. 훌륭한 설계자는 미래에 구체적으로 어떤 변경이 발생할 것인지를 예측하지 않는다. 좋은 설계는 나중에라도 변경할 수 있는 여지를 남겨 놓는 설계다.
설계를 하는 목적은 나중에 설계하는 것을 허용하는 것이며, 설계의 일차적인 목표는 변경에 소요되는 비용을 낮추는 것이다[Metz 2012].
객체지향은 객체의 구조에 집중하고 기능이 객체의 구조를 따르게 만든다. 시스템 기능은 더 작은 책임으로 분할되고 적절한 객체에 분배되기 때문에 기능이 변경되더라도 객체 간의 구조는 그대로 유지된다.
객체 지도는 빠르게 변화하는 기능을 수용할 수 있는 자리를 제공한다. 객체 지도는 안정적이며 재사용가능하면서도 범용적이다.
객체 지도를 만들어라. 기능은 지도에 표시된 길을 따라 자연스럽게 흘러갈 것이다.
두 가지 재료: 기능과 구조
- 구조: 사용자나 이해관계자들이 도메인(domain)에 관해 생각하는 개념과 개념들 간의 관계로 표현한다.
- 기능: 사용자의 목표를 만족시키기 위해 책임을 수행하는 시스템의 행위로 표현한다.
- 도메인 모델링: 구조를 수집하고 표현하기 위한 기법
- 유스케이스 모델링: 기능을 수집하고 표현하기 위한 기법
쉽게 예상할 수 있는 것처럼 두 가지 모델링 활동의 결과물을 각각 유스케이스와 도메인 모델이라고 한다.
안정적인 재료: 구조
- 도메인 모델
모든 소프트웨어는 사용자의 필요성을 충족시키기 위해 존재한다. 소프트웨어를 사용하는 사람들은 자신이 관심을 가지고 있는 특정한 분야의 문제를 해결하기 위해 소프트웨어를 사용한다. 이처럼 사용자가 프로그램을 사용하는 대상 분야를 도메인이라고 한다[Evans 2003].
도메인 모델에서 모델이란 대상을 단순화하고 추상화시켜서 표현한 것이다.
도메인 모델: 사용자가 프로그램을 사용하는 대상 영역에 관한 지식을 선택적으로 단순화하고 의식적으로 구조화한 형태
도메인 모델은 단순히 다이어그램이 아니다. 도메인 모델은 이해관계자들이 바라보는 멘탈 모델(Mental Model)이다. 멘탈 모델이란 사람들이 자기 자신, 다른 사람, 환경, 자신이 상호작용하는 사물들에 대해 갖는 모형이다.
도널드 노먼(Donald Norman)은 제품을 설계할 때 제품에 관한 모든 것이 사용자들이 제품에 대해 가지고 있는 멘탈 모델과 정확해야 한다고 주장한다.
노먼은 위의 그림과 같이 멘탈 모델을 사용자 모델, 디자인 모델, 시스템 이미지의 세 가지로 구분한다.
- 사용자 모델: 사용자가 제품에 대해 가지고 있는 개념들의 모습
- 디자인 모델: 설계자가 마음 속에 갖고 있는 시스템에 대한 개념화
- 시스템 이미지: 최종 제품
사용자의 모델과 디자인 모델이 동일하다면 이상적이겠지만 사용자와 설게자는 직접적으로 상호작용할 수 없으며 단지 최종 제품인 시스템 그 자체를 통해서만 의사소통할 수 있다. 따라서 설게자는 디자인 모델을 기반으로 만든 시스템 이미지가 사용자 모델을 정확하게 반영하도록 노력해야 한다.
도메인 모델은 도메인에 대한 사용자 모델, 디자인 모델, 시스템 이미지를 포괄하도록 추상화한 소프트웨어 모델이다. 따라서 도메인 모델은 소프트웨어에 대한 멘탈 모델이다.
- 도메인의 모습을 담을 수 있는 객체지향
도널드 노먼의 주장을 요약하면 최종 제품은 사용자의 관점을 반영해야 한다는 것이다. 이것은 소프트웨어 개발에도 동일하게 적용할 수 있다. 최종 코드는 사용자가 도메인을 바라보는 관점을 반영해야 한다. 이것은 곧 애플리케이션이 도메인 모델을 기반으로 설계돼야 한다는 것을 의미한다.
따라서 도메인 모델의 세 가지 측면을 모두 모델링할 수 있는 유사한 모델링 패러다임을 사용할수록 소프트웨어 개발이 쉬워질 것이다. 객체지향은 이런 요구사항을 가장 범용적으로 만족시킬 수 있는 거의 유일한 모델링 패러다임이다.
객체지향을 이용하면 도메인에 대한 사용자 모델, 디자인 모델, 시스템 이미지 모두가 유사한 모습을 유지하도록 만드는 것이 가능하다.
객체지향의 이러한 특징을 연결완전성이라고 한다.
- 표현적 차이
다시 한번 강조하지만 소프트웨어 객체는 현실 객체에 대한 추상화가 아니다. 소프트웨어 객체는 현실 객체를 모방한 것이 아니라 은유를 기반으로 재창조한 것이다. 따라서 소프트웨어 객체는 현실 객체가 갖지 못한 특정을 가질 수도 있고 현실 객체가 하지 못하는 행동을 할 수도 있다.
비록 소프트웨어 객체가 현실 객체를 왜곡한다고 하더라도 소프트웨어 객체는 현실 객체의 특성을 토대로 구축된다.
이처럼 소프트웨어 객체와 현실 객체 사이의 의미적 거리를 가리켜 표현적 차이 또는 의미적 차이라고 한다. 핵심은 은유를 통해 현실 객체와 소프트웨어 객체 사이의 차이를 최대한 줄이는 것이다.
그렇다면 우리가 은유를 통해 투영해야 하는 대상은 무엇인가? 그것은 바로 사용자가 도메인에 대해 생각하는 개념들이다. 즉, 소프트웨어 객체를 창조하기 위해 우리가 은유해야 하는 대상은 바로 도메인 모델이다.
도메인 모델을 기반으로 설계하고 구현하는 것은 사용자가 도메인을 바라보는 관점을 그대로 코드에 반영할 수 있게 한다. 결과적으로 표현적 차이는 줄어들 것이며, 사용자의 멘탈 모델이 그대로 코드에 녹아 스며들게 될 것이다.
표현적 차이가 중요한 이유는 소프트웨어를 이해하고 수정하기 쉽게 만들어주기 때문이다. 코드의 구조가 도메인의 구조를 반영하기 때문에 도메인을 이해하면 코드를 이해하기가 훨씬 수월해진다. 도메인 속의 개념과 관계가 코드 속에 녹아 있기 때문에 도메인이 알려주는 길을 따라가면 코드 속에서 길을 잃지 않을 수 있다. 결국 도메인 모델은 코드 안에 존재하는 미로를 헤쳐나갈 수 있는 지도를 제공한다.
- 불안정한 기능을 담는 안정적인 도메인 모델
도메인 모델을 기방느로 코드를 작성하는 두 번째 이유는 도메인 모델이 제공하는 구조가 상대적으로 안정적이기 때문이다. 도메인 모델의 핵심은 사용자가 도메인을 바라보는 관점을 반영해 소프트웨어를 설계하고 구현하는 것이다. 도메인에 대한 사용자의 관점을 반영해야 하는 이유는 사용자들이 누구보다도 도메인의 '본질적인' 측면을 가장 잘 이해하고 있기 때문이다. 사용자들은 도메인을 구성하는 중요한 개념과 개념 간의 관계를 가장 잘 알고 있는 사람들이다.
본질적이라는 것은 변경이 적고 비교적 그 특성이 오랜 시간 유지된다는 것을 의미한다. 소프트웨어의 개발의 가장 큰 적은 변경이며, 변경은 항상 발생한다는 사실을 기억하라. 사용자 모델에 포함된 개념과 규칙은 비교적 변경될 확률이 적기 때문에 사용자 모델을 기반으로 설계와 코드를 만들면 변경에 쉽게 대처할 수 있을 가능성이 커진다.
비록 도메인 모델이 도메인과 관련된 중요한 개념과 관계를 보여준다고 해도, 실제로 사용자에게 중요한 것은 도메인 모델이 아니라 소프트웨어의 기능이다. 따라서 사용자에게 제공할 기능을 기술한 정보가 필요하다. 객체지향 커뮤니티에서는 오래 전부터 소프트웨어의 기능을 기술하기 위해 유스케이스라는 유용한 기법을 사용해 왔다.
불안정한 재료: 기능
- 유스케이스
기능적 요구사항이란 시스템이 사용자에게 제공해야 하는 기능의 목록을 정리한 것이다. 시스템이 사용자에게 기능을 제공하는 이유는 무엇인가? 사용자들이 시스템을 통해 달성하고자 하는 '목표'가 존재하기 때문이다. 따라서 훌륭한 기능적 요구사항을 얻기 위해서는 목표를 가진 사용자와 사용자의 목표를 만족시키기 위해 일련의 절차를 수행하는 시스템 간의 '상호작용' 관점에서 시스템을 바라봐야 한다.
사용자의 목표를 달성하기 위해 사용자와 시스템 간에 이뤄지는 상호작용의 흐름을 텍스트로 정리한 것을 유스케이스라고 한다.
유스케이스는 시스템의 이해관계자들 간의 계약을 행위 중심으로 파악한다. 유스케이스는 이해관계자들 중에서 일차 액터라 불리는 행위자의 요청에 대한 시스템의 응답으로서, 다양한 조건 하에 있는 시스템의 행위를 서술한다. 일차 벡터는 어떤 목표를 달성하기 위해 시스템과의 상호작용을 시작한다. 시스템은 모든 이해관계자들의 요구에 응답하고 이해관계를 보호해야 한다. 특별한 요청과 관계되는 조건에 따라 서로 다른 일련의 행위 혹은 시나리오가 전개될 수 있다. 유스케이스는 이렇게 서로 다른 시나리오를 묶어 준다.
'일차 액터(primary actor)'란 시스템의 서비스 중에 하나를 요청하는 이해관계자로, 하나의 목표를 가지고 유스케이스를 시작하는 액터를 의미한다. 일반적으로 시스템과 연동하는 외부 시스템 역시 일차 액터의 범주에 포함시킨다.
유스케이스의 가치는 사용자들의 목표를 중심으로 시스템의 기능적인 요구사항들을 이야기 형식으로 묶을 수 있다는 점이다.
- 유스케이스의 특성
위의 그림은 유스케이스의 몇 가지 중요한 특성을 잘 보여준다.
1. 유스케이스는 사용자와 시스템 간의 상호작용을 보여주는 '텍스트'다.
유스케이스는 다이어그램이 아니다. 중요한 것은 유스케이스 안에 포함돼 있는 상호작용의 흐름이다.
2. 유스케이스는 하나의 시나리오가 아니라 여러 시나리오들의 집합이다.
시나리오(scenario)는 유스케이스를 통해 시스템을 사용하는 하나의 특정한 이야기 또는 경로다.
시나리오는 유스케이스 인스턴스(use case instance)라고도 한다.
3. 유스케이스는 단순한 피처(feature) 목록과 다르다.
피처는 시스템이 수행해야 하는 기능의 목록을 단순하게 나열한 것이다.
4. 유스케이스는 사용자 인터페이스와 관련된 세부 정보를 포함하지 말아야 한다.
유스케이스는 자주 변경되는 사용자 인터페이스 요소는 배제하고 사용자 관점에서 시스템의 행위에 초점을 맞춘다.
이처럼 사용자 인터페이스를 배제한 유스케이스 형식을 본질적인 유스케이스(essential use case)라고 한다.
5. 유스케이스는 내부 설계와 관련된 정보를 포함하지 않는다.
유스케이스의 목적은 연관된 시스템의 기능을 이야기 형식으로 모으는 것이지 내부 설계를 설명하는 것이 아니다.
- 유스케이스는 설계 기법도, 객체지향 기법도 아니다
유스케이스가 단지 사용자가 바라보는 시스템의 외부 관점만을 표현한다는 점에 주목하라. 유스케이스는 시스템이 외부에 제공해야 하는 행위만 포함하기 때문에 유스케이스로부터 시스템의 내부 구조를 유추할 수 있는 방법은 존재하지 않는다. 사실 유스케이스는 객체지향과도 상관이 없다. 유스케이스는 객체지향 이외의 패러다임에서도 적용이 가능하다.
유스케이스와 객체의 구조 사이에는 커다란 간격이 존재한다. 둘 사이의 간격을 자동으로 없앨 수 있는 어떤 방법도 존재하지 안흔ㄴ다. 유스케이스를 객체로 변환하는 작업은 순수하게 창조적이고 예술적인 작업이다. 유스케이스를 기반으로 객체의 구조를 쉽게 추출할 수 있다는 어설픈 설명에 속지 마라. 유스케이스는 객체의 구조나 책임에 대한 어떤 정보도 제공하지 않는다.
물론 유스케이스 텍스트 안에서 도메인 모델에서 사용할 용어에 대한 힌트를 얻을 수도 있다. 유스케이스 안에 포함된 이야기 흐름 속에 나타나는 계좌, 이자, 금액과 같은 단어들은 도메인 모델에 포함될 수 있는 개념이나 속성에 관한 다양한 정보를 제공한다. 그러나 유스케이스 안에 도메인 모델을 구축할 수 있는 모든 정보가 포함돼 있다는 착각에 빠지지 말길 바란다. 유스케이스 안에는 영감을 불러일으킬 수 있는 약간의 힌트만이 들어있을 뿐이다.
재료 합치기: 기능과 구조의 통합
- 도메인 모델, 유스케이스, 그리고 책임-주도 설계
불안정한 기능을 안정적인 구조 안에 담음으로써 변경에 대한 파급효과를 최소화하는 것은 훌륭한 객체지향 설계자가 갖춰야 할 기본적인 설계 능력이다. 도메인 모델은 안정적인 구조를 개념화하기 위해, 유스케이스는 불안정한 기능을 서술하기 위해 가장 일반적으로 사용되는 도구다. 변경에 유연한 소프트웨어를 만들기 위해서는 유스케이스에 정리된 시스템의 기능을 도메인 모델을 기반으로 한 객체들의 책임으로 분배해야 한다.
앞에서 살펴본 책임-주도 설계는 이 지점부터 적용된다. 지금까지는 시스템이 사용자에게 제공할 기능이 있다는 가정하에 객체들 간의 협력을 설계했지만 사실 협력의 출발을 장식하는 첫 번째 메시지는 시스템의 기능을 시스템의 책임으로 바꾼 후 얻어진 것이다.
시스템에 할당된 커다란 책임은 이제 시스템 안의 작은 규모의 객체들이 수행해야 하는 더 작은 규모의 책임으로 세분화된다.
책임-주도 설계 방법은 시스템의 기능을 역할과 책임을 수행하는 객체들의 협력 관계로 바라보게 함으로써 두 가지 기본 재료인 유스케이스와 도메인 모델을 통합한다. 물론 책임-주도 설계를 위해 유스케이스와 도메인 모델이 반드시 필요한 것은 아니고 유스케이스와 도메인 모델이 책임-주도 설계에서만 사용되는 것은 아니다.
중요한 것은 견고한 객체지향 애플리케이션을 개발하기 위해서는 사용자의 관점에서 시스템의 기능을 명시하고, 사용자와 설계자가 공유하는 안정적인 구조를 기반으로 기능을 책임으로 변환하는 체계적인 절차를 따라야 한다는 것이다.
- 기능 변경을 흡수하는 안정적인 구조
앞에서 설명한 것처럼 도메인 모델을 기반으로 객체 구조를 설계하는 이유는 도메인 모델이 안정적이기 때문이다. 도메인 모델이 안정적인 이뉴는 도메인 모델을 구성하는 요소가 다음과 같은 특징을 띠기 때문이다.
- 도메인 모델을 구성하는 개념은 비즈니스가 없어지거나 완전히 개편되지 않는 한 안정적으로 유지된다.
(Ex: 정기예금 도메인에서 정기예금과 계좌, 이자율, 이자란 개념은 정기예금이란 금융상품이 없어지거나 완전히 개편되지 않는 한 안정적으로 유지되는 개념이다) - 도메인 모델을 구성하는 개념 간의 관계는 비즈니스 규칙을 기반으로 하기 때문에 비즈니스 정책이 크게 변경되지 않는 한 안정적으로 유지된다.
(Ex: 계좌와 이자 간의 0..1 관계는 이와 같은 핵심적인 비즈니스 규칙이 변경되지 않는 한 도일하게 유지될 것이다)
도메인 모델의 이같은 특징은 도메인 모델을 중심으로 객체 구조를 설계하고 유스케이스의 기능을 객체의 책임으로 분배하는 기본적인 객체지향 설계 방식의 유연함을 보여 준다. 비즈니스 정책이나 규칙이 변경되지 않는 한 시스템의 기능이 변경되더라도 객체 간의 관계는 일정하게 유지된다.
기능적인 요구사항이 변경될 경우 책임과 객체 간의 대응 관계만 수정될 뿐이다. 이것은 변경에 대한 파급효과를 최소화하고 요구사항 변경에 유연하게 대응할 수 있는 시스템을 구축할 수 있게 한다.
객체지향의 가장 큰 장점은 도메인을 모델링하기 위한 기법과 도메인을 프로그래밍하기 위해 사용하는 기법이 동일하다는 점이다. 따라서 도메인 모델링에서 사용한 객체와 개념을 부드럽게 프로그래밍 설계에서의 객체와 클래스로 변환할 수 있다[연결완전성].
객체지향이 강력한 이유는 연결완전성의 역방향 역시 성립한다는 것이다. 즉, 코드의 변경으로부터 도메인 모델의 변경 사항을 유추할 수 있다. 객체지향에서는 도메인 모델과 코드 모두 동일한 모델링 패러다임을 공유하기 때문에 코드의 수정이 곧 모델의 수정이 된다. 이처럼 모델에서 코드로의 부드러운 흐름을 의미하는 것을 [가역성(reversibility)]이라고 한다.
도메인 모델은 문서나 다이어그램이 아니다. 도메인 모델은 사람들의 머릿속에 들어있는 공유된 멘탈 모델이다.
따라서 별도의 문서나 다이어그램을 가지고 있지 않더라도 사람들의 머릿속에 위와같은 그림과 유사한 모델이 공유될 수 있다면 그것으로 충분하다. 사람들이 동일한 용어와 동일한 개념을 이용해 의사소통하고 코드로부터 도메인 모델을 유추할 수 있게 하는 것이 도메인 모델의 진정한 목표다.
안정적인 도메인 모델을 기반으로 시스템의 기능을 구현하라. 도메인 모델과 코드를 밀접하게 연관시키기 위해 노력하라. 그것이 유지보수하기 쉽고 유연한 객체지향 시스템을 만드는 첫걸음이 될 것이다.
'Study > 객체지향의 사실과 오해' 카테고리의 다른 글
5장: 책임과 메시지 (0) | 2021.09.10 |
---|---|
4장: 역할, 책임, 협력 (0) | 2021.08.18 |
3장: 타입과 추상화 (0) | 2021.08.16 |
댓글