TL;DR
- GRASP라고 부르는 책임 할당 패턴을 설명합니다.
- 2장에서 소개한 영화 예매 시스템의 설계를 책임 할당의 관점에서 설명하고 4장에서 구현한 절차적 프로그래밍 방식과 비교합니다.
- 책임을 중심으로 설계를 이끌어가는 것이 캡슐화, 응집도, 결합도의 관점에서 설계를 개선한다는 사실을 이해하게 될 것입니다.
- 5장에서는 2장에서 소개한 코드의 설계 과정을 한 걸음씩 따라가 보면서 객체에 책임을 할당하는 기본적인 원리를 살펴봅니다.
- 데이터 중심의 설계는 행동보다 데이터를 먼저 결정하고 협력이라는 문맥을 벗어나 고립된 객체의 상태에 초점을 맞추기 때문에 캡슐화를 위반하기 쉽고, 요소들 사이의 결합도가 높아지며, 코드를 변경하기 어려워집니다
- 데이터 중심 설계로 인해 발생하는 문제점을 해결할 수 있는 가장 기본적인 방법은 데이터가 아닌 책임에 초점을 맞추는 것입니다.
- 책임에 초점을 맞춰서 설계할 때 직명하는 가장 큰 어려움은 어떤 객체에게 어떤 책임을 할당할지는 결정하기가 쉽지 않다는 것입니다. 책임 할당 과정은 일종의 트레이드오프 활동입니다. 동일한 문제를 해결할 수 있는 다양한 책임 할당 방법이 존재하며, 어떤 방법이 최선이니즌 상황과 문맥에 따라 달라집니다. 따라서 올바른 책임을 할당하기 위해서는 다양한 관점에서 설계를 평가할 수 있어야 합니다.
- GRASP은 책임 할당의 어려움을 해결하기 위한 답을 제시해 줄 것입니다. GRASP 패턴을 이해하고 나면 응집도와 결합도, 캡슐화 같은 다양한 기준에 따라 책임을 할당하고 결과를 트레이드오프할 수 있는 기준을 배우게 될 것입니다.
01. 책임 주도 설계를 향해
- 데이터 중심의 설계에서 책임 중심의 설계로 전환하기 위해서는 다음의 두 가지 원칙을 따라야 합니다.
- 데이터보다 행동을 먼저 결정합니다.
- 협력이라는 무맥 안에서 책임을 결정합니다.
- 두 원칙의 핵심은 설계를 진행하는 동안 데이터가 아니라
객체의 책임
과협력
에 초점을 맞추라는 것입니다.
데이터보다 행동을 먼저 결정하라
- 객체에게 중요한 것은 데이터가 아니라
외부에 제공하는 행동
입니다. - 클라이언트의 관점에서 객체의 수행하는 행동이란 곧 객체의 책임을 의마합니다.
- 객체는 협력에 참여하기 위해 존재하며 협력 안에서 수행하는 책임이 객체의 존재가치를 증명합니다.
- 데이터는 객체가 책임을 수행하는 데 필요한 재료를 제공할 뿐입니다. 객체지향에 갓 입문한 사람들이 가장 만힝 저지르는 실수가 바로 객체의 행동이 아니라 데이터에 초점을 맞추는 것입니다. 너무 이른 시기에 데이터에 초점을 맞추면 객체의 캡슐화가 약화되기 때문에 낮은 응집도와 높은 결합도를 가진 객체들로 넘쳐나게 됩니다. 그 결과로 얻게 되는 것은 변경에 취약한 설계입니다.
- 우리에게 필요한 것은 객체의 데이터에서 행동으로 무게 중심을 옮기기 위한 기법입니다. 가장 기본적인 해결 방법은 객체를 설계하기 위한 질문의 순서를 바꾸는 것입니다. 데이터 중심의 설계에서는 ‘이 객체가 포함해야 하는 데이터가 무엇인가’를 결정한 후에 ‘데이터를 처리하는 데 필요한 오퍼레이션은 무엇인가’를 결정합니다. 반면 책임 중심 설계에서는 ‘이 객체가 수향해야 하는 책임은 무엇인가’를 결정한 후에 ‘이 책임을 수행하는 데 필요한 데이터는 무엇인가’를 결정합니다. 다시 말해 책임 중심의 설계에서는 객체의 행동, 즉 책임을 먼저 결정한 후에 객체의 상태를 결정한다는 것입니다.
- 객체지향 설계에서 가장 중요한 것은 절적한 객체에게 적절한 책임을 할당하는 능력입니다.
협력이라는 문맥 안에서 책임을 결정하라
- 객체에게 할당된 책임의 품질은 협력에 적합한 정도로 결정됩니다. 객체에게 할당된 책임이 협력에 어울리지 않는다면 그 책임은 나쁜 것입니다.
객체의 입장에서는 책임이 조금 어색해 보이더라도 협력에 적합하다면 그 책임은 좋은 것입니다
. 책임은 객체의 입장이 아니라 객체가 참여하는 협력에 적합해야 합니다. - 협력을 시작하는 주체는 메시지 전송자이기 때문에 협력에 적합한 책임이란 메시지 수신자가 아니라 메시지
전송자에게 적합한 책임
을 의미합니다. 다시 말해서 메시지를 전송하는 클라이언트의 의도에 적합한 책임을 할당해야 합니다. - 협력에 적합한 책임을 수확하기 위해서는 객체를 결정한 후에 메시지를 선택하는 것이 아니라 메시지를 결정한 후에 객체를 선택해야 합니다. 메시지가 존재하기 때문에 그 메시지를 처리할 객체가 필요한 것입니다. 객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택하게 해야 합니다.
- ‘이 클래스가 필요하다는 점은 알겠는데 이 클래스는 무엇을 해야 하지?‘라고 질문하지 않고 ‘메시지를 전송해야 하는데 누구에게 전송하지?‘라고 질문하는 것. 설계의 핵심 질문을 바꾸는 것이
메시지 기반 설계
로 향하는 첫걸음입니다. 객체를 가지고 있기 때문에 메시지를 보내는 것이 아니라 메시지를 전송하기 때문에 객체를 갖게 된 것입니다. - 메시지가 클라이언트 의도를 표현합니다. 객체를 결정하기 전에 객체가 수신할 메시지를 먼저 결정합니다. 클라이언트는 어떤 객체가 메시지를 수신할지 알지 못합니다. 클라이언트는 단지 임의의 객체가 메시지를 수신할 것이라는 사실을 믿고 자신의 의도를 표현한 메시지를 전송할 뿐입니다. 그리고 메시지를 수신하기로 결정된 객체는 메시지를 처리할
책임
을 할당 받게 됩니다. - 메시지를 먼저 결정하기 때문에 메시지 송신자는 메시지 수신에 대한 어떠한 가정도 할 수 없습니다. 메시지 전송자의 관점에서 메시지 수신자가 깔끔하게 캡슐화되는 것입니다. 데이터에 집중하는 데이터 중심의 설계가 캡슐화에 취약한 반면 협력이라는 문맥 안에서 메시지에 집중하는 책임 중심의 설계는 캡슐화의 원리를 지키기가 훨씬 쉬워집니다. 책임 중심의 설계가 응집도가 높고 결합도가 낮으며 변경하기 쉽다고 말하는 이유입니다.
- 객체에게 적절한 책임을 할당하기 위해서는
협력이라는 문맥
을 고려해야 합니다. 협력이라는 문맥에서 적절한 책임이란 곧 클라이언트의 관점에서 적절한 책임을 의미합니다. 올바른 객체지향 설계는 클라이언트가 전송할 메시지를 결정한 후에야 비로소 객체의 상태를 저장하는 데 필요한 내부 데이터에 관해 고민하기 시작합니다. 책임 중심의 설계
에서는 협력이라는 문맥 안에서 객체가 수행할 책임에 초점을 맞춥니다.- 지금까지 설명한 두 원칙은
책임 주도 설계
방법의 핵심과 거의 동일합니다.
책임 주도 설계
- 책임 주도 설계의 흐름
- 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악합니다.
- 시스템 책임을 더 작은 책임으로 분할 합니다.
- 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당합니다.
- 객체가 책임을 수행하는 도중 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾습니다.
- 해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 합니다.
- 책임 주도 설계의 핵심은 결정할 후에 책임을 수행할 객체를 결정하는 것입니다. 그리고 협력에 참여한느 객체들의 책임이 어느 정도 정리될 때까지 객체의 내부 상태에 대해 관심을 가지지 않는 것입니다.
02. 책임 할당을 위한 GRASP 패턴
- GRASP은
General Responsibility Assignment Software Pattern(일반적인 책임 할당을 위한 소프트웨어 패턴)
의 약자로 객체에게 책임을 할당할 때 지침으로 삼을 수 있는 원칙들의 집합을 패턴 형식으로 정리한 것입니다.
도메인 개념에서 출발하기
- 설계를 시작하기 전에 도메인에 대한 개략적인 모습을 그려 보는 것이 유용합니다. 도메인 안에는 무수히 많은 개념들이 존재하며 이 도메인 개념들을 책임 할당의 대상으로 사용하면 코드에 도메인의 모습을 투영하기가 좀 더 수월해집니다. 따라서 어떤 책임을 할당해야 할 때 가장 먼저 고민해야 하는 유력한 후보는 바로 도메인 개념입니다.
- 설계를 시작하는 단계에서는 개념들의 의미와 관계가 정확하거나 완벽한 필요가 없습니다. 단지 우리에게는 출발점이 필요할 뿐입니다. 이 단계에서는 책임을 할당받을 객체들의 종류와 관계에 대한 유용한 정보를 제공할 수 있다면 충분합니다. 시작 지점에는 설계를 시작하기 위한 참고할 수 있는 개념들의 모음 정도로 간주합니다. 중요한 것은 설계를 시작하는 것이지 도메인 개념들을 완벽하게 정히하는 것이 아닙니다.
도메인 개념을 정리하는 데 너무 많은 시간을 들이지 말고 빠르게 설계와 구현을 진행합니다
.
올바른 도메이 모델이란 존재하지 않는다
- 도메인 모델이 올바른 구현을 이끌어낼 수만 있다면 정답은 없습니다.
- 많은 사람들이 도메인 모델은 구현과는 무관하다고 생각하지만 이것은 도메인 모델의 개념을 오해한 것에 불과합니다. 도메인 모델은 도메인을 개념적으로 표현한 것이지만 그 안에 포함된 개념과 관계는 구현의 기반이 돼야 합니다. 이것은 도메인 모델이 구현을 염두에 두고 구조화되는 것이 바람직하다는 것을 의미합니다. 반대로 코드의 구조가 도메인을 바라보는 관점을 바꾸기도 합니다.
도메인 모델의 구조가 코드의 구조에 영향을 미칩니다
. 유연성이나 재사용성 등과 같이 실제 코드를 구현하면서 얻게 되는 통찰이 역으로 도메인에 대한 개념을 바꾸기도 합니다.올바른 도메인 모델이란 존재하지 않습니다
. 필요한 것은 도메인을 그대로 투영한 모델이 아니라구현에 도움이 되는 모델
입니다. 다시 말해서 실용적이면서도 유용한 모델이 답입니다.
정보 전문가에게 책임을 할당하라
- 책임 주도 설계 방식의 첫 단계는 애플리케이션이 제공해야 하는 기능을
애플리케이션의 책임
으로 생각하는 것입니다. 이 책임을 애플리케이션에 대해 전송된 메시지로 간주하고 이 메시지를 책임질 첫 번째 객체를 선택하는 것으로 설계를 시작합니다. - 메시지는 메시지를 수신할 객체가 아니라 메시지를 전송할 객체의 의도를 반영해서 결정해야 합니다. 따라서 첫 번째 질문은
메시지를 전송할 객체는 무엇을 원하는가?
입니다. 메시지를 결정하면 그 메시지에 적합한 객체를 선택해야 합니다. 두 번째 질문은메시지를 수신할 적합한 객체는 누구인가
입니다. 이 질문에 답하기 위해서는 객체가 상태와 행동을 통합한 캡슐화의 단위라는 사실에 집주앻야 합니다. 객체는 자신의 상태를 스스로 처리하는 자율적인 존재여야 합니다. 객체의 책임과 책임을 수행하는 데 필요한 상태는 동일한 객체 안에 존재해야 합니다. 따라서 객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것입니다. GRASP에서는 이를INFORMATION EXPERT(정보 전문가)
패턴이라고 부릅니다. - INFORMATION EXPERT 패턴은 객체가 자신이 소유하고 있는 정보와 관련된 작업을 수행한다는 일반적인 직관을 표현한 것입니다. 여기서 이야기하는 정보는 데이터와 다르다는 사실에 주의해야 합니다. 책임을 수행하는 객체가 정보를
알고
있다고 해서 그 정보를저장
하고 있을 필요하는 없습니다. 객체는 해당 정보를 제공할 수 있는 다른 객체를 알고 있거나 필요한 정보를 계산해서 제공할 수도 있습니다.어떤 방식이건 정보 전문가가 데이터를 반드시 저장하고 있을 필요는 없다
는 사실을 이해하는 것이 중요합니다. - 메시지를 수신하고 책임을 수행하는 데 필요한 작업을 구상해보고 스스로 처리할 수 없는 작업이 무엇인지 가립니다. 만약 스스로 처리할 수 없는 작업이 있다면 외부에 도움을 요청해야 합니다. 이 요청이 외부로 전송해야하는 새로운 메시지가 되고, 이 메시지가 새로운 객체의 책임으로 할당됩니다. 이 같은 연쇄적인 메시지 전송과 수신을 통해 협력 공동체가 구성되는 것입니다.
- INFORMATION EXPERT 패턴은 객체에게 책임을 할당할 때 가장 기본이 되는 책임 할당 원칙입니다. INFORMATION EXPERT 패턴은 객체란 상태와 행동을 함께 가지는 단위라는 객체지향의 가장 기본적인 원리를 책임 할당의 관점에서 표현합니다. INFORMATION EXPERT 패턴을 따르는 것만으로도 자율성이 높은 객체들로 구성된 협력 공동체를 구축할 가능성이 높아지는 것입니다.
INFORMATION EXPERT 패턴
- 책임을 정보 전문가, 즉 책임을 수행하는 데 필요한 정보를 가지고 있는 객체에게 할당합니다.
- INFORMATION EXPERT은 객체가 자율적인 존재여야 한다는 사실을 다시 한번 상기시킵니다. 정보를 알고 있는 객체만이 책임을 어떻게 수행할지 스스로 결정할 수 있기 때문입니다. INFORMATION EXPERT 패턴을 따르면 정보와 행동을 최대환 가까운 곳에 위치시키기 때문에
캡슐화
를 유지할 수 있습니다. 필요한 정보를 가진 객체들로 책임이 분산되기 때문에 더 응집력 있고, 이해하기 쉬워집니다. 따라서 높은 응집도가 가능합니다. 결과적으로 결합도가 낮아져서 간결하고 유지보수하기 쉬운 시스템을 구축할 수 있습니다.
높은 응집도와 낮은 결합도
설계는 트레이드오프 활동
입니다. 동일한 기능을 구현할 수 있는 무수히 많은 설계가 존재합니다. 따라서 실제로 설계를 진행하다 보면 몇 가지 설계 중에서 한 가지를 선택해야 하는 경우가 빈번하게 발생합니다. 이 경우에는 올바른 책임을 할당하기 위해 INFORMATION EXPERT 패턴 이외의 다른 책임 할당 패턴들을 함께 고려할 필요가 있습니다.- 높은 응집도와 낮은 결합도는 객체에 책임을 할당할 때 항상 고려해야 하는 기본 원리입니다. 책임을 할당할 수 있는 다양한 대안들이 존재한다면
응집도와 결합도의 측면에서 더 나은 대안을 선택
하는 것이 좋습니다. 다시 말해 두 협력 패턴 중에서 높은 응집도와 낮은 결합도를 얻을 수 있는 설계가 있다면 그 설계를 선택해야 한다는 것입니다. - GRASP에서는 이를
LOW COUPLING(낮은 결합도)
패턴과HIGH COHESION(높은 응집도)
패턴이라고 부릅니다. - LOW COUPLING 패턴과 HIGH COHESION 패턴은 설계를 진행하면서 책임과 협력의 품질을 검토하는 데 사용할 수 있는 중요한 평가 기준입니다. 책임을 할당하고 코드를 작성하는 매순간마다 LOW COUPLING과 HIGH COHESION의 관점에서 전체적인 설계 품질을 검토하면서 단순화하면서도 재사용 가능하고 유연한 설계를 얻을 수 있을 것입니다.
LOW COUPLING
- 의존성을 낮추고
변화에 영향
을 줄이며 재사용성을 증가시키는 방법 - 설계의 전체적인 결합도를 낮게 유지되도록 책임을 할당합니다.
- 낮은 결합도는 모든 설계 결정에서 염두에 둬야 하는 원리입니다. 다시 말해 설계 결정을 평가할 때 적용할 수 있는 평가원리입니다. 현재의 책임 할당을 검토하거나 여러 설계 대안들이 있을 때 낮은 결합도를 유지할 수 있는 설계를 선택합니다.
HIGH COHESION
복잡성을 관리
할 수 있는 수준으로 유지하는 방법- 높은 응집도를 유지할 수 있게 책임을 할당합니다.
- 낮은 결합도처럼 높은 응집도 역시 모든 설계 결정에서 염두에 둬야 할 원리입니다. 다시 말해 설계 결정을 평가할 때 적용할 수 있는 평가원리입니다. 현재의 책임 할당을 검토하고 있거나 여러 설계 대안 중 하나를 선택해야 한다면 높은 응집도를 유지할 수 있는 설계를 선택합니다.
창조자에게 객체 생성 책임을 할당하라
- GRASP의
CREATOR(창조자)
패턴은 이 같은 경우에 사용할 수 있는 책임 할당 패턴으로서 객체를 생성할 책임을 어떤 객체에게 할당하지에 대한 지침을 제공합니다.
CREATOR 패턴
- 객체 A를 생성해야 할 때 아래 조건을 최대한 많이 만족하는 B에게 객체 생성 책임을 할당합니다.
- B가 A 객체를 포함하거나 참조합니다.
- B가 A 객체를 기록합니다.
- B가 A 객체를 긴밀하게 사용합니다.
- B가 A 객체를 초기화하는 데 필요한 데이터를 가지고 있습니다.(이 경우 B는 A에 대한 정보 전문가입니다.)
- CREATOR 패턴의 의도는 어떤 방식으로든 생성되는 객체와 연결되거나 관련될 필요가 있는 객체에 해당 객체를 생성할 책임을 맡기는 것입니다. 생성될 객체에 대해 잘 알고 있어야 하거나 그 객체를 사용해야 하는 객체는 어떤 방식으올든 생성될 객체와 연결될 것입니다. 다시 말해서 두 객체는 서로
결합
됩니다. - 이미 결합돼 있는 객체에게 생성 책임을 할당하는 것은 설계의 전체적인 결합도에 영향을 미치지 않습니다. 결과적으로 CREATOR 패턴은
이미 존재하는 객체 사이의 관계 사이의 관계를 이용하기 때문에 설계가 낮은 결합도를 유지
할 수 있게 합니다.
올바른 설계를 하고 있는지 궁금하다면?
- 실제 설계는 코드를 작성하는 동안 이루어집니다. 그리고 협력과 책임이 제대로 동작하는지 확인할 수 있는 유일한 방법은 코드를 작성하고 실행해 보는 것뿐입니다.
- 올바른 설계를 하고 있는지 궁금하다면 코드를 작성합니다.
03. 구현을 통한 검증
- 메시지가 객체를 선택하도록 책임 주도 설계의 방식을 따르면 캡슐화와 낮은 결합도라는 목표를 비교적 손쉽게 달성할 수 있습니다.
DiscountCondition 개선하기
- 가장 큰 문제점은 변경에 취약한 클래스를 포함하고 있다는 것입니다. 변경에 취약한 클래스란
코드를 수정해야 하는 이유를 하나 이상 가지는 클래스
입니다. - 응집도가 낮다는 것은 서로 연관성이 없는 기능이나 데이터가 하나의 클래스 안에 뭉쳐져 있다는 것을 의미합니다. 따라서 낮은 응집도가 초래하는 문제를 해결하기 위해서는
변경의 이유에 따라 클래스를 분리해야 합니다
. 일반적으로 설계를 개선하는 작업은 변경의 이유가 하나 이상인 클래스를 찾는 것으로부터 시작하는 것이 좋습니다. - 코드를 통해 변경의 이유를 파악할 수 있는 첫 번째 방법은
인스턴스 변수가 초기화되는 시점
을 살펴보는 것입니다. 응집도가 높은 클래스는 인스턴스를 생성할 때모든 속성을 함께 초기화
합니다. 반면 응집도가 낮은 클래스는 객체의 속성 중 일부만 초기화하고 일부는 초기화 되지 않는 상태로 남겨집니다. 클래스 속성이 서로 다른 시점에 초기화 되거나 일부만 초기화된다는 것은 응집도가 낮다는 증거입니다. 따라서함께 초기화되는 속성을 기준으로 코드를 분리해야 합니다
. - 코드를 통해 변경의 이유를 파악할 수 있는 두 번째 방법은
메서드들이 인스턴스 변수를 사용하는 방식
을 살펴보는 것입니다. 모든 메서드가 객체의 모든 속성을 사용한다면 클래스의 응집도는 높다고 볼 수 있습니다. 반면 메서드들이 사용하는 속성에 따라 그룹이 나뉜다면 클래스의 응집도가 낮다고 볼 수 있습니다. 클래스의 응집도를 높이기 위해서는속성 그룹과 해당 그룹에 접근하는 메서드 그룹을 기준으로 코드를 분리해야 합니다
.
클래스 응집도 판단하기
- 클래스의 응집도를 판단할 수 있는 세 가지 방법
- 클래스가 하나 이상의 이유로 변경돼야 한다면 응집도가 낮은 것입니다. 변경의 이유를 기준으로 클래스를 분리합니다.
- 클래스의 인스턴스를 초기화하는 시점에 경우에 따라 서로 다른 속성들을 초기화하고 있다면 응집도가 낮은 것입니다. 초기화되는 속성의 그룹을 기준으로 클래스를 분리합니다.
- 메서드 그룹이 속성 그룹을 사용하는지 여부로 나뉜다면 응집도가 낮은 것입니다. 이들 그룹을 기준으로 클래스를 분리합니다.
- 일반적으로 응지보가 낮은 클래스는 이 세 가지 문제를 동시에 가지는 경우가 대부분입니다. 메서드의 크기가 너무 커서 긴 코드 라인 속에 문제가 숨겨져 명확하게 보이지 않을 수도 있습니다. 이 경우 긴 메서드를 응집도 높은 작은 메서드로 잘게 분해해 나가면 숨겨져 있던 문제점이 명확하게 드러나는 경우가 많습니다.
타입 분리하기
- 두 타입을 두 개의 클래스로 분리합니다. 개별 클래스들의 응집도가 향상되고 클래스를 분리함으로써 코드의 품질을 높이는 데에는 성공했습니다. 하지만 이는 두 개의 서로 다른 클래스의 인스턴스 모두와 협력할 수 있어야 한다는 설계의 관점에서 전체적인 결합도가 높아집니다. 또한 새로운 조건을 추가하기가 더 어려워졌습니다. 클래스를 분리하기 전에는 수신자 내부 구현만 수정하면 전송자는 아무런 영향도 미치지 않았지만 수정 후에는 조건을 추가하려면 전송자도 함께 수정해야 하빈다. 수신자의 인장에서 보면 응집도가 높아졌지만 변경과 캡슐화라는 관점에서 보면 전체저긍로 설계의 품질이 나빠지고만 것입니다.
다형성을 통해 분리하기
- 역할은 협력 안에서
대체 가능성
을 의미하기 때문에 역할의 개념을 적용하면 구체적인 클래스는 알지 못한 채 오직 역할에 대해서만 결합되도록 의존성을 제한할 수 있습니다. - 역할을 사용하면 객체의 구체적인 타입을
추상화
할 수 있습니다. 자바에서는 일반적으로 역할을 구현하기 위해 추상 클래스나 인터페이스를 사용합니다. - 역할을 대체할 클래스들 사이에서
구현을 공유
해야 할 필요가 있다면 추상 클래스를 사용하면 됩니다. - 구현을 공유할 필요 없이 역할을 대체하는 객체들의 책임만 정의하고 싶다면 인터페이스를 사용하면 됩니다.
- 객체의 암시적인 타입에 따라 행동을 분기해야 한다면 암시적인 타입을 명시적인 클래스로 정의하고 행동을 나눔으로써 응집도 문제를 해결할 수 있습니다. 다시 말해 객체의 타입에 따라 변하는 행동이 있다면 타입을 분리하고 행동을 각 타입의 책임으로 할당하라는 것입니다. GRASP에서느 이를
POLYMORPHISM(다형성)
패턴이라고 부릅니다.
POLYMORPHISM 패턴
- 객체의 타입에 따라 변하는 로직이 있을 때 변하는 로직을 담당할 책임을 할당하기 위해서는 타입을 명시적으로 정의하고 각 타입에 다형적으로 행동하는 책임을 할당합니다.
- 조건에 따른 변화는 프로그램의 기본 논리입니다. 프로그램을 if ~ else 또는 switch ~ case 등의 조건 논리를 사용해서 설계한다면 새로운 변화가 일어난 경우 조건 논리를 수정해야 합니다. 이것은 프로그램을 수정하기 어렵고 변경에 취약하게 만듭니다.
- POLYMORPHISM 패턴은 객체의 타입을 검사해서 타입에 따라 여러 대안들을 수행하는 조건적인 논리를 사용하지 말라고 경고합니다. 대신 다형성을 이용해 새로운 변화를 다루기 쉽게 확장하라고 권고합니다.
변경으로부터 보호하기
- 두 개의 서로 다른 변경이 두 개의 서로 다른 클래스 안으로 캡슐화됩니다.
- 변경을 캡슐화하도록 책임을 할당하는 것을 GRASP에서는
PROTECTED VARIATIONS(변경 보호)
패턴이라고 부릅니다. - 클래스를 변경에 따라 분리하고 인터페이스를 이용해 변경을 캡슐화하는 것은 설계의 결합도와 응집도를 향상시키는 매우 강력한 방법입니다.
- 하나의 클래스가 여러 타입의 행동을 구현하고 있는 것처럼 보인다면 클래스를 분해하고
POLYMORPHISM
패턴에 따라 책임을 분산시킵니다. 예측 가능한 변경으로 인해 여러 클래스들이 불안정해진다면PROTECTED VARIATIONS
패턴에 따라 안정적인 인터페이스 뒤로 변경을 캡슐화 합니다. 적절한 두 패턴을 조합하면 코드 수정의 파급 효과를 조절할 수 있고 변경과 확장에 유연하게 대처할 수 있는 설계를 얻을 수 있을 것입니다.
PROTECTED VARIATIONS
- 변화가 예상되는 불안정한 지점들을 식별하고 그 주위에 안정된 인터페이스를 형성하도록 책임을 할당하면 객체, 서브시스템 그리고 시스템이 다른 요소에 나쁜 영향을 미치지 않도록 방지할 수 있습니다.
- PROTECTED VARIATIONS 패턴은 책임 할당의 관점에서 캡슐화를 설명한 것입니다.
설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화하라
라는 객체지향의 오랜 격언은 PROTECTED VARIATIONS 패턴의 본질을 잘 설명해 줍니다. 우리가 캡슐화해야 하는 것은 변경입니다.변경될 가능성이 높다면 캡슐화
합니다.
Movie 클래스 개선하기
- POLYMORPHISM 패턴을 사용해 서로 다른 행동을 타입별로 분리하면 다형성의 혜택을 누릴 수 있습니다.
- PROTECTED VARIATIONS 패턴을 이용해 타입의 종류를 안정적인 인터페이스 뒤로 캡슐화할 수 있습니다.
- 각 클래스는 응집도가 높고 다른 클래스와 최대한 느슨하게 결합돼 있습니다. 클래스는 작고 오직 한 가지 일만 수행합니다. 책임은 적절하게 분배돼 있습니다. 이것이 책임을 중심으로 협력을 설계 할 때 얻을 수 있는 혜택입니다.
- 데이터 중심의 설계는 정반대의 길을 걷습니다. 데이터 중심의 설계는 데이터와 관련된 클래스의 내부 구현이 인터페이스에 여과 없으 노출되기 때문에 캡슐화를 지키기 어렵습니다. 이로 인해 응집도가 낮고 결합도가 높으며 변경에 취약한 코드가 만들어질 가능성이 높습니다.
- 데이터가 아닌 책임을 중심으로 설계하라는 것입니다. 객체에게 중요한 것은 상태가 아니라 행동입니다. 객체지향 설계의 기본은 책임과 협력에 초점을 맞추는 것입니다.
도메인의 구조가 코드의 구조를 이끈다
- 도메인 모델은 단순히 설계에 필요한 용어를 제공하는 것을 넘어 코드의 구조에도 영향을 미칩니다.
변경 역시 도메인 모델의 일부
입니다. 도메인 모델에는 도메인 안에서 변하는 개념과 이들 사이의 관계가 투영돼 있어야 합니다. 그리고 이 직관이 우리의 설계가 가져야 하는 유연성을 이끌 것입니다.구현을 가이드 할 수 있는 도메인 모델을 선택
합니다. 객체지향은 도메인의 개념과 구조를 반영한 코드를 가능하게 만들기 때문에 도메인의 구조가 이끌어 내는 것은 자연스러울뿐만 아니라 바람직한 것입니다.
변경과 유연성
설계를 주도하는 것은 변경
입니다.- 개발자로서 변경에 대비할 수 있는 두 가지 방법이 있습니다.
- 하나는 코드를 이해하고 수정하기 쉽도록 최대한
단순한게 설계
하는 것입니다. - 다른 하나는 코드를 수정하지 않고도 변경을 수용할 수 있도록 코드를 더
유연하게 만드는 것
입니다. - 대부분 경우에 전자가 더 좋은 방법이지만 유사한 변경이 반복적으로 발생하고 있다면 복잡성이 상승하더라도 유연성을 추가하는 두 번쨰 방법이 더 좋습니다.
- 하나는 코드를 이해하고 수정하기 쉽도록 최대한
- 설계를
상속
을 이용하는 경우 새로운 정책이 추가될 때마다 인스턴스를 생성하고, 상태를 복사하고, 식별자를 관리하는 코드를 추가하는 일은 번거로울뿐만 아니라 오류가 발생하기도 쉽습니다. 이 경우 코드의 복잡성이 높아지더라도 정책의 변경을 쉽게 수용할 수 있게 코드를 유연하게 만다는 것이 더 좋은 방법입니다. 이를 위한 방법은 상속 대신합성
을 사용하는 것입니다. 합성을 사용할 경우 새로운 정책이 추가되더라도 정책을 변경하는 데 필요한 추가적인 코드를 작성할 필요가 없습니다. - 유연성은 의존성 관리 문제입니다. 요소들 사이의 의존성의 정도가 유연성의 정도를 결정합니다. 유연성의 정도에 따라 결합도를 조절할 수 있는 능력은 객체지향 개발자가 갖춰야 하는 중요한 기술 중 하나입니다.
- 객체지향 프로그래밍 언어를 이용해 절차형 프로그램을 작성하는 대부분의 이유가 바로 책임 할당의 어려움에서 기인합니다.
- 일단 절차형 코드로 실행되는 프로그램을 빠르게 작성한 후 완성된 코드를 객체지향적 코드로 변경하는 방법도 있습니다.
코드의 구조가 도메인의 구조에 대한 새로운 통찰력을 제공한다
- 코드의 구조가 바뀌면 도메인에 대한 관점도 함께 바뀝니다. 정책을 자유롭게 변경할 수 있다는 것은 도메인에 포함된 중요한 요구사항입니다. 이 요구사항을 수용하기 위해 정책이라는 개념을 코드 상에서 명시적으로 드러냈다면 도메인 모델 역시 코드의 관점에 따라 바뀌어야 합니다. 따라서 도메인 모델은 코드의 구조에 따라 수정됩니다. 이 도메인 모델은 도메인에 포함된 개념과 관계뿐만 아니라 도메인이 요구하는 유연성도 정확하게 반영합니다.
- 도메인 모델은 단순히 도메인의 개념과 관계를 모아 놓은 것이 아닙니다. 도메인 모델은 구현과 밀접한 관계를 맺어야 합니다. 도메인 모델은 코드에 대한 가이드를 제공할 수 있어야 하며 코드의 변화에 발맞춰 함께 변화해야 합니다. 도메인 모델으,ㄹ 코드와 분리된 막연한 무엇으로 생각하면 안 됩니다.
04. 책임 주도 설계의 대안
- 설계를 진행하는 동안 데이터가 아닌 책임 관점에서 사고하기 위해서는 충분한 경험과 학습이 필요합니다. 그러나 어느 정도 경험을 쌓은 숙련된 설계자조차도 적절한 책임과 객체를 선택하는 일에 어려움을 느끼고는 합니다.
- 책임과 객체 사이에서 방황할 때 돌파구를 찾기 위해 선택하는 방법은
최대한 빠르게 목적한 기능을 수행하는 코드를 작성하는 것
입니다. 아무것도 없는 상태에서 책임과 협력에 관해 고민하기 보다는 일단 실행되는코드를 얻고 난 후에 코드 상에 명확하게 드러나는 책임들을 올바를 위치로 이동
시키는 것입니다. 주로 객체지향 설계에 대한 경험이 부족한 개발자들과페어 프로그래밍
을 할 때나 설계의 실마리가 풀리지 않을 때 이런 방법을 사용하는데 생각보다 훌륭한 설계를 얻게 되는 경우가 종종 있습니다. - 주의할 점은 코드를 수정한 후에 겉으로 드러나는 동작이 바뀌어서는 안 된다는 것입니다. 캡슐화를 향상 시키고, 응집도를 높이고, 결합도를 낮춰야 하지만 동작은 그대로 유지해야 합니다. 이처럼 이해하기 쉽고 수정하기 쉬운 소프트웨어로 개선하기 위해 겉으로 보이는 동작은 바꾸지 않은 채 내부 구조를 변경하는 것을
리팩터링(Refactoring)
이라고 부릅니다.
메서드 응집도
- 긴 메서드는 다양한 측면에서 코드의 유지보수에 부정적인 영향을 미칩니다. 한마디로 말해서 긴 메서드는 응집도가 낮기 때문에 이해하기도 어렵고 재사용하기도 어려우며 변경하기 어렵습니다. 마이클 페더스는 이런 메스드를
몬스터 메소드(monster method)
라고 부릅니다.- 어떤 일을 수행하는지 한눈에 파악하기 어렵기 때문에 코드를 전체적으로 이해하는 데 너무 많은 시간이 걸립니다.
- 하나의 메서드 안에서 너무 많은 작업을 처리하기 때문에 변경이 필요할 때 수정해야 할 부분을 찾기 어렵습니다.
- 메서드 내부의 일부 로직만 수정하더라도 메서드의 나머지 부분에서 버그가 발생할 확률이 높습니다.
- 로직의 일부만 재사용하는 것이 불가능합니다.
- 코드를 재사용하는 유일한 방법은 원하는 코드를 복사해서 붙여넣는 것뿐이므로 코드 중복을 초해하기 쉽습니다.
- 응집도가 낮은 메서드는 로직의 흐름을 이해하기 위해 주석이 필요한 경우가 대부분입니다. 메서드가 명령문들의 그룹으로 구성되고 각 그룹에 주석을 달아야 할 필요가 있다면 그 메서드의 응집도는 낮은 것입니다. 주석을 추가하는 대신 메서드를 작게 분해해서 각 메서드의 응집도를 높입니다.
- 클래스의 응집도와 마찬가지로 메서드의 응집도를 높이는 이유도 변경과 관련이 깊습니다.
응집도 높은 메서드는 변경되는 이유가 단 하나여야 합니다
. 클래스가 작고, 목적이 명확한 메서드들로 구성돼 있다면 변경을 처리하기 위해 어떤 메서드를 수행해야 하는지를 판단할 수 있습니다. 또한 메서드의 크기가 작고 목적이 분명하기 때문에 재사용하기도 쉽습니다. 작은 메서드들로 조합된 메서드는 마치 주석들을 나열한 것으로 보이기 때문에 코드를 이해하기도 쉽습니다. - 짧고, 이해하기 쉬운 이름으로 된 메소드의 장점 많습니다. 만약, 큰 메서드에서 익숙해져 있다면 메서드를 잘게 나누는 데는 약간의 시간이 걸릴 것입니다. 작은 메서드는 실제로 이름을 잘 지었을 때만 그 진가가 드러나므로, 이름을 지을 때 주의해야 합니다. 메서드의 길이가 중요하지 않고 중요한 것은 메서드의 이름과 메서드 몸체의 의미적 차이입니다. 뽑아내는 것이 코드를 더욱 명확하게 하면 새로 만든 메서드의 이름이 원래 코드의 길이보다 길어져도 뽑아냅니다.
- 첫째, 메서드가 잘게 나눠져 있을 때 다른 메서드에서 사용될 확률이 높아집니다.
- 둘째, 고수준의 메서드를 볼 때 일련의 주석을 읽는 것 같은 느낌이 들게 할 수 있습니다.
- 셋째, 메서드가 잘게 나눠져 있을 때 오버라이딩하는 것도 훨씬 쉽습니다.
- 객체로 책임을 분배할 때 가장 먼저 할 일은
메서드를 응집도 있는 수준으로 분해
하는 것입니다. 긴 메서드를 작고 응집도 높은 메서드로 분리하면 각 메서드를 적절한 클래스로 이동하기가 더 수월해지기 때문입니다. - 일반적으로 명확성의 가치가 클래스의 길이보다 더 중요합니다.
- 메서드를 분리하고 나면 public 메서드는 상위 수준의 명세를 읽는 것 같은 느낌이 듭니다.
- 메서드가 어떤 일을 하는지를 한눈에 알아볼 수 있습니다. 심지어 메서드의 구현이 주석을 모아 놓은 것처럼 보이기까지 합니다.
- 코드를 작은 메서드들로 분해하면 전체적인 흐름을 이해하기도 쉬워집니다. 동시에 너무 많은 세부사항을 기억하도록 강요하는 코드는 이해하기도 어렵습니다. 큰 메서드를 작은 메서드들로 나누면 한 번에 기억해야 하는 정보를 줄일 수 있습니다. 더 세부적인 정보가 필요하다면 그때 각 메서드의 세부적인 구현을 확인하면 되기 때문입니다.
- 수정 후의 코드는 변경하기도 더 쉽습니다. 각 메서드는 단 하나의 이유에 의해서만 변경됩니다.
- 작고, 명확하며, 한 가지 일에 집중하는 응집도 높은 메서든느 변경 가능한 설계를 이끌어 내는 기반이 됩니다. 이런 메서드들이 하나의 변경 이유를 가지도록 개선될 때 결과적으로 응집도 높은 클래스가 만들어집니다.
- 응집도를 높이기 위해서는 변경의 이유가 다른 메서드들을 적절한 위치로 분배해야 합니다. 적절한 위치란 바로 각 메서드가 사용하는 데이터를 정의하고 있는 클래스를 의미합니다.
객체를 자율적으로 만들자
- 자신이 소유하고 있는 데이터를 자기 스스로 처리하도록 만드는 것이 자율적인 객체를 만드는 지름길입니다. 따라서 메서드가 사용하는 데이터를 저장하고 있는 클래스로 메서드를 이동시키면 됩니다.
- 어떤 데이터를 사용하는지 가장 쉽게 알수 있는 방법은 메서드 안에서 어떤 클래스의
접근자 메서드
를 사용하는지 파악하는 것입니다. - 메서드를 다른 클래스로 이동시킬 때는
인자에 정의된 클래스
중 하나로 이동하는 경우가 일반적입니다. - 데이터를 사용하는 메서드를 데이터를 가진 클래스로 이동시키고 나면 캡슐화와 높은 응집도, 낮은 결합도를 가지게 되는 설계를 얻게 됩니다.
- 메서드를 이동할 캡슐화, 응집도, 결합도의 측면에서 이동시킨 메서드의 적절성을 판단하기 바랍니다. 메서드를 이동시키면서 어떤 메서드가 어떤 클래스에 위치해야 하는지에 대한 감을 잡아가면서 다양한 기능을 책임 주도 설계 방식에 따라 설계하고 구현해 보기 바랍니다.
- 책임 주도 설계 방법이 익숙하지 않다면 일단 데이터 중심으로 구현한 후 이를 리팩터링하더라도 유사한 결과를 얻을 수 있다는 것입니다.
처음부터 책임 주도 설계 방법을 따르는 것보다 동작하는 코드를 작성한 후에 리팩터링 하는 것이 더 훌륭한 결과물을 낳을 수도 있습니다
. 캡슐화, 결합도, 응집도를 이해하고 훌륭한 객체지향 원칙을 적용하기 위해 노력한다면 책임 주도 설계 방법을 단계적으로 따르지 않더라도 유연하고 깔끔함 코드를 얻을 수 있을 것입니다.
메시지 기반 설계, 책임 중심 설계, 책임 주도 설계는 다른 것일까?
- 3가지 모두 큰 맥락에서는 같다고 볼 수 있으나 세부 디테일에서 조금씩 차이가 있는거 같습니다.
메시지 기반 설계
의 반대는클래스 기반의 설계
로 데이터 중심 설계와 책임 중심 설계와 거의 같으나 클래스와 메시지, 데이터와 책임이라는 기준점이 조금 다른거 같습니다.- 클래스 기반 설계에서 메시지 기반 설계로의 자리바꿈은 우리가 해오던 설계 활동의 전환점입니다.
- ‘이 클래스가 필요하다는 점은 알겠는데 이 클래스는 무엇을 해야 하지?‘라고 질문하지 않고 ‘메시지를 전송해야 하는데 누구에게 전송하지?‘라고 질문하는 것. 설계의 핵심 질문을 바꾸는 것이
메시지 기반 설계
로 향하는 첫걸음입니다. 객체를 가지고 있기 때문에 메시지를 보내는 것이 아니라 메시지를 전송하기 때문에 객체를 갖게 된 것입니다.
데이터 중심 설계
에서책임 중심 설계
로 전환하기 위해서는 다음의 두 가지 원칙을 따라야 합니다.- 책임 중심 설계는 데이터보다 행동을 먼저 결정합니다.
- 협력이라는 문맥 안에서 책임을 결정합니다.
- 책임 중심 설계의 두 원칙은 책임 주도 설계의 핵심과 거의 동일합니다.
책임 주도 설계
은 두 가지 원칙 뿐만 아니라 흐름도 중요합니다.- 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악합니다.
- 시스템 책임을 더 작은 책임으로 분할 합니다.
- 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당합니다.
- 객체가 책임을 수행하는 도중 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾습니다.
- 해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 합니다.
- 중심은
중요하고 기본이 되는 부분
을 뜻하며 주도하다 라는 말은주동적인 처지가 되어 이끌다
라는 의미입니다. 말 뜻에서 봤을 때 중심 보다는 주도가 설계에서는 조금 더 많은 의미를 가지지 않을까 예상해 봅니다. 따라서 지켜야 하는 부분들이 책임 중심 설계보다 책임 주도 설계가 더 많은 것 같습니다. - 책임이 객체가 수신할 수 있는 메시지의 기반이 됩니다.
메시지와 메소드
- 협력을 시작하는 주체는 메시지 전송자이기 때문에 협력에 적합한 책임이란 메시지 수신자가 아니라 메시지 전송자에게 적합한 책임을 의미합니다. 다시 말해서 메시지를 전송하는 클라이언트의 의도에 적합한 책임을 할당해야 합니다.
- 협력에 적합한 책임을 수확하기 위해서는 객체를 결정한 후에 메시지를 선택하는 것이 아니라 메시지를 결정한 후에 객체를 선택해야 합니다. 메시지가 존재하기 때문에 그 메시지를 처리할 객체가 필요한 것입니다. 객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택하게 해야 합니다.
그래서 메시지와 메소드가 어떻게 다른걸까요 ? (사실 2장 객체지향 프로그래밍에 나왔던 내용입니다)
- 객체는 다른 객체의 인터페이스에 공개된 행동을 수행하도록
요청(request)
할 수 있습니다. 요청을 받은 객체는 자율적인 방법에 따라 요청을 처리한 후응답(response)
합니다. - 객체가 다른 객체와 상호작용할 수 있는 유일한 방법은
메시지를 전송(send a message)
하는 것뿐입니다. 다른 객체에서 요청이 도착할 때 해당 객체가메시지를 수신(receive a message)
했다고 이야기 합니다. 메시지를 수신한 객체는 스스로의 결정에 따라 자율적으로 메시지를 처리할 방법을 결정합니다. 이처럼 수신된 메시지를 처리하기 위한 자신만의 방법을메서드(method)
라고 부릅니다. 메시지와 메서드를 구분하는 것은 매우 중요
합니다. 객체지향 패러다임이 유연하고, 확장 가능하며, 재사용 가능한 설계를 낳는다는 명성을 얻게 된 배경에는 메시지와 메서드를 명확하게 구분하는 것도 단단히 한몫합니다. 메시지와 메서드의 구분에서부터다형성(polymorphism)
의 개념이 출발합니다.
6장 메시지와 인터페이스에서의 메시지와 메소드
- 메시지(message)는 객체들이 협력하기 위해 사용할 수 있는 유일한 의사소통 수단입니다.
- 메시지를 수신했을 때 실제로 실행되는 함수 또는 프로시저를
메서드
라고 부릅니다.- 메시지를 수신했을 때 실제로 실행되는 코드는 메서드
- 기술적인 관점에서 객체 사이의 메시지 전송은 전통적인 방식의 함수 호출이나 프로시저 호출과는 다릅니다. 전통적인 방식의 개발자는 어떤 코드가 실행될지를 정확하게 알고 있는 상황에서 함수 호출이나 프로시저 호출 구문을 작성합니다. 다시 말해 코드의 의미가 컴파일 시점과 실행 시점에 돌일하다는 것입니다. 반면 객체는 메시지와 메서드라는 두 가지 서로 다른 개념을 실행 시점에 연결해야 하기 때문에 컴파일 시점과 실행 시점의 의미가 달라질 수 있습니다.
- 객체지향이 메시지 전송과 메서드 호출을 명확하게 구분한다는 사실이 여러분을 모호함의 덫으로 밀어 넣을 수도 있습니다.
- 메시지와 메서드의 구분은 메시지 전송자와 메시지 수신자가 느슨하게 결합될 수 있게 합니다.
- 메시지 전송자는 사진이 어떤 메시지를 전송해야 하는지만 알면 됩니다. 수신자가 어떤 클래스의 인스턴스인지, 어떤 방식으로 요청을 처리하는지 모르더라도 원할한 협력이 가능합니다.
- 메시지 수신자 역시 누가 메시지를 전송하는지 알 필요가 없습니다. 단지 메시지가 도착했다는 사실만 알면 됩니다. 메시지 수신자는 메시지를 처리하기 위해 메서드를 결정할 수 있는 자율권을 누립니다.
용어정리
- 메시지 : 객체가 다른 객체와 협력하기 위해 사용하는 의사소통 메커니즘. 일반적으로 객체의 오퍼레이션이 실행되도록 요청하는 것을 ‘메시지 전송’이라고 부릅니다. 메시지는 협력에 참여하는 전송장하 수신자 양쪽 모두를 포함하는 개념입니다.
- 오퍼레이션 : 객체가 다른 객체에게 제공하는 추상적인 서비스입니다. 메시지가 전송자와 수신자 사이의 협력 관계를 강조하는 데 비해오퍼레이션은 메시지를 수신하는 객체의 인터페이스를 강조합니다. 다시 말해 메시지 전송자는 고려하지 않은 채 메시지 수신자의 관점만을 다룹니다. 메시지 수신이란 메시지에 대응되는 객체의 오퍼레이션을 호출하는 것을 의미합니다.
- 메서드 : 메시지에 응답하기 위해 실행되는 코드 블록을 메서드라고 부릅니다. 메서드는 오퍼레이션의 구현입니다. 동일한 오퍼레이션이라고 해도 메서드는 다를 수 있습니다. 오퍼레이션과 메서드의 구분을 다형성의 개념과 연결됩니다.
- 퍼블릭 인터페이스 : 객체가 협력에 참여하기 위해 외부에서 수신할 수 있는 메시지의 묶음. 클래스의 퍼블릭 메서드들의 집합이나 메시지의 집합을 가리키는 데 사용됩니다. 객체를 설계할 때 가장 중요한 것은 훌륭한 퍼블릭 인터페이스를 설계하는 것입니다.
- 시그니처 : 시그니처는 오퍼레이션이나 메서드의 명세를 나타낸 것으로, 이름과 인자의 목록을 포함합니다. 대부분의 언어는 시그니처의 일부로 반환 타입을 포함하지 않지만 반환 타입을 시그니처의 일부로 포함하는 언어도 존재합니다.