GRASP는 General Responsibility Assignment Software Patterns 의 앞글자를 딴 것이다.
직역하면 대략 '일반 책임 할당 소프트웨어 패턴' 정도 된다.
패턴이 들어가는 걸 보면 알 수 있듯이, GoF의 디자인패턴과도 관련이 있다.
여기에서는 Craig Larman의 9가지 원칙을 이야기 하고 있는데, 이 각각들은 Coupling을 낮추거나, Cohesion과 Reusability를 높이고 Flexibility를 높이기 위한 원칙들이다.
우선.. 'OOAD를 사용하여 소프트웨어를 구축하는 일반적인 작업'이 어떤 순서로 이뤄지는지 다시 상기해보자.
1. 요구사항 식별
2. 도메인모델 만들기
3. 디자인모델 만들기
- 소프트웨어 클래스들에 메소드와 속성 추가
- 객체들 간 상호작용 정의
컴퓨터 프로그래머라면 누구나 알고 있듯이 OOAD 방식의 소프트웨어 구축은 이런 절차로 이뤄진다.
이 와중에 GRASP는 협업하는 객체들에게 책임을 어떻게 할당하면 될지를 안내해주는 역할을 한다.
낮은(또는 느슨한) 결합도를 추구하라.
높은 결합도를 가지면 다음과 같은 단점들이 있다.
- 종속 클래스 변경으로 인한 강제 변경
- 고립된 상태에서는 이해하기 어려움
- 재사용 어려움
높은 응집도를 추구하라.
높은 응집도의 장점은 아래와 같은 것들이 있다.
- 쉽게 이해하고 유지 관리 가능
- 코드 재사용
- 낮은 결합도
이러한 방향을 추구하는 객체지향 소프트웨어 또는 시스템을 설계하기 위해, 어떻게 해야 할지?
GRASP는 아래 9개의 '원칙' 으로 이야기 하고있다.
Creator
Who creates an instance?
문제 : 누가 클래스 A를 만들것인가?
해결책 : "아래 내용중 하나이상을 만족하는 클래스 B에게 그 책임을 할당하라"
- B는 A를 포함(contains)하거나, A를 집계(aggregates) 하고 있다.
- B는 A를 기록한다.
- B는 A를 긴밀하게 사용한다.
- B가 A를 초기화하는데 필요한 데이터를 가지고 있다.
만약 여러 클래스가 해당된다면, " B는 A를 포함(contains)하거나, A를 집계(aggregates) 하고 있다."에 해당하는 클래스가 조금 더 적합한 대상이라고 검토해볼 수 있다.
이점 : 낮은 커플링을 지원
관련 GoF패턴
- Abstract Factory
- Singleton
Information Expert
Basic principles to assign responsibility
어떤 특정 역할을 수행할 클래스를 정해서 어떻게 책임을 할당할 지, 위치를 결정하는데 사용되는 원칙.
책임할당에서 가장 많이 사용되는 원칙이다. (Basic principles to assign responsibility)
일반적으로 아래와 같은 순서로 결정을 진행한다.
- 주어진 역할/책임이 무엇인지 확인
- 그 책임을 이행하는데 필요한 정보가 무엇인지 결정
- 해당 정보가 저장되는 위치를 결정
- 그것을 이행하는데 필요한 정보를 가장 많이 가지고 있는 클래스에게 책임을 할당
객체는 데이터 + 처리로직으로 구성되어 있다. 데이터를 감추고 싶다면 자신의 로직안에서만 데이터를 사용해서 처리를 진행하고, 외부에는 그 기능만을 메소드 형태로 제공한다.
정보는 다른 객체들에 걸쳐서 퍼져있고, 그것들을 상호작용해야 한다.
이점 : 정보 캡슐화 => 낮은 커플링을 지원
Controller
First object beyond the UI layer received and coordinates(= controls)
문제 : UI계층에서 받은 어떤 입력/명령 을 넘겨받을 첫번째 객체는 무엇인가? 그 객체는 시스템 처리(system operation)로 입력/명령을 조정 (coordintes) 또는 제어(controls) 해야 한다.
해결책 : 아래 중 한가지를 선택해서 책임을 할당한다.
- 첫번째 옵션 - 전체 "시스템", "루트 객체", 소프트웨어가 실행되는 장치 또는 주요 하위 시스템 (= facade controller)
- 두번째 옵션 - 시스템 작업이 발생하는 사용 사례 시나리오 (= use case or session controller)
이점 : GUI에 앱 로직을 없게 할 수 있음 (= 잠재적으로 컴포넌트의 재사용성을 높일 수 있음)
컨트롤러는 둘 이상의 use case에서도 사용 가능하다. 예로, 사용자 생성 / 사용자 삭제 / 사용자 수정 이라는 이벤트 세 개의 개별 컨트롤러 대신 UserController라는 단일 클래스로 구성해서 처리 하는 것도 가능. 컨트롤러는 수행해야 할 작업을 다른 개체에 위임해야 하며, 활동을 조정하거나 통제해야 하지만 컨트롤러 자체가 많은 일을 해서는 안 됨.
컨트롤러는 너무 많은 책임을 할당받으면 비대해지게 된다. 그러지 않도록 관리가 필요하다. 비대해 진다 싶으면 다음을 고려해 볼수 있다.
- 컨트롤러의 추가 (facade → use-case controllers)
- 다른 객체에 위임하기
Low Coupling Pattern
How to reduce the impact of change?
문제: 어떻게 변경의 영향도를 감소시킬 것인가?
이점 : 낮은 커플링을 지원
High Cohesion
How to keep objects understandable and manageable support Low Coupling?
문제 :객체를 이해하기 쉽고 관리하기 쉽게 유지하는 방법은 무엇일까? 낮은 결합도를 지원하려면 어떻게 해야 할까?
모듈 또는 객체의 응집도(coheision)를 높게 하면, 객체들 간의 낮은 결합도를 유지할 수 있다. 그렇게 되면 이해하기 쉽고 관리가 쉬워진다.
프로그램을 클래스와 서브시스템으로 나누는 것은 시스템의 응집도를 높이는 하나의 예이다.
응집도가 높다는 것은 하나의 객체에는 밀접하게 관련이 있는 책임들만 주어진다는 것이다. 반대로 응집도가 낮다는 것은 주어진 요소들이 관련없는 책임을 너무 많이 갖는 상황을 말한다.
예를들어, Manager라는 클래스에 사용자를 관리하는 책임과, 판매물품을 관리하는 책임, 매장 정보를 관리하는 책임, 판매이력을 관리하는 책임을 모두 위임한다면, 그건 응집도가 낮다고 할 수 있다.
그러나, UserManager에 사용자를 관리하는 책임을 위임하고, StoreManager에 매장정보를 관리하는 책임을 위임하며, ProductManager에는 판매물품 관리 책임을, SaleHistoryManager에는 판매이력 관리 책임을 각각 위임한다면, 이건 응집도가 높은 설계라고 할 수 있을 것이다.
응집도가 낮으면 이해하기 어렵고 재사용도 어렵게 되며, 유지보수도 힘들어 질 수있다.
이점 : 낮은 커플링을 지원
Pure Fabrication
"behavior" class that does not represent a problem domain concept.
문제 :
- 어떤 특정 책임(기능이나 데이터)을 소프트웨어의 어떤 "객체"에게 할당해야 할지 명확하지 않다.
- 기존에 있는 객체들(예: 고객 객체, 주문 객체, 상품 객체 등)에 이 책임을 추가하면, 그 객체의 역할이 너무 많아지거나(낮은 응집도), 다른 객체들과 너무 복잡하게 얽히게 되어(높은 결합도) 설계 원칙을 위반하게 될 것 같다.
- 즉, 기존 객체들만으로는 책임 할당에 어려움을 겪고 있다.
해결책:
- 새로운, 인위적인 객체를 만든다.
- 이 객체는 현실 세계의 어떤 개념(고객, 주문 등)을 나타내는 것이 아니라, 오직 소프트웨어 설계의 목적을 위해 순수하게 '만들어진(Fabricated)' 객체이다.
- 이 가상의 객체에게 높은 응집도를 가지는 일련의 책임들을 할당한다.
- 이 객체는 특정 행동(behavior)을 수행하기 위해 존재하게된다.
이점: 낮은 커플링을 지원
핵심 의미:
예를들면, Sale 인스턴스를 DB에 저장하고 싶다고 가정하자. Information Expert 원칙에 따라 DB관련 operation들을 Sale클래스에 넣게 되면 응집도가 낮아진다. 재사용성도 나빠지고, DB에 저장하려는 다른 클래스가 있다면 그것들에도 DB관련 operation 이 중복될 것이다.
이럴 때, 인위적으로 DB에 저장하는 동작을 책임지는 새로운 클래스(Fabricated class)를 만들어 해결하면 된다.
Pure Fabrication은 소프트웨어 설계의 유연성, 재사용성, 유지보수성을 높이기 위해 필요하다면, 현실 세계에 존재하지 않는 '가상의' 객체를 만들어서 특정 책임들을 모아두는 원칙입니다. 예를 들어, 데이터를 저장하고 불러오는 복잡한 로직이 있다고 해봅시다. 이 로직을 '고객' 객체에 넣으면 고객 객체가 너무 많은 일을 하게 되고, '데이터베이스' 객체에 넣자니 순수한 데이터 처리를 넘어선 로직이 섞입니다. 이럴 때 'DataLoader'나 'DataProcessor'와 같은 순수하게 역할만을 위해 만들어진 객체를 만들고, 그 객체에게 데이터 로딩/처리 책임을 모아주는 것입니다. 이 'DataLoader' 객체는 현실 세계에 '데이터 로더'라는 실체가 있는 것이 아니라, 소프트웨어 설계를 위해 탄생한 'Pure Fabrication' 객체입니다.
이렇게 하면 기존 객체들은 자신의 본래 책임에만 집중하여 응집도를 높게 유지하고, 서로 불필요하게 얽히는 결합도를 낮출 수 있습니다. 또한, 이렇게 분리된 가상의 객체는 나중에 다른 곳에서도 동일한 기능이 필요할 때 쉽게 재사용할 수 있게 됩니다.
요약 : "어떤 기능을 어디에 넣을지 애매하고, 기존 객체에 넣으면 설계가 나빠질 것 같다면? 그 기능을 위한 '가상의 전담팀' 객체를 새로 만들어서 맡겨라!" 이것이 Pure Fabrication의 핵심.
Polymorphism
Who is responsible when behavior varies by type?
문제 : 유형에 따라 동작이 다를 경우 누가 책임을 져야 할까?
관련된 대안이나 동작이 유형(Type or Class)에 따라 다를 경우, 동작이 달라지는 유형에 다형성 연산을 사용하여 동작에 대한 책임을 할당하자. 객체의 Type으로 분기를 처리하는 영역에서는 명시적인 분기 대신 다형성 연산을 적극적으로 사용해야 한다.. Object-Oriented 시스템의 특징이자 장점인 상속(Inheritance)와 Polymorphism(다형성)을 아낌없이 사용하길 권장한다는 원칙이다.
이점 : 재사용성 증가
관련 GoF 패턴
- Strategy
- Template method
Indirection
How to assign responsibilities to avoid direct coupling?
문제 :직접 결합을 피하기 위해 책임을 할당하는 방법은 무엇일까?
결합도가 높은 두 객체 사이에 중간 요소를 도입하고 중재 책임을 할당하면, 두 요소간의 결합도를 낮추고, 재사용 가능성까지도 지원할 수 있다.
이점 : 낮은 커플링
관련 GoF 패턴
- Adapter
- Facade
- Proxy
- Mediator
Protected variations
Identify points of predicted variation of instability
문제 :다른 요소에 바람직하지 않은 영향을 미치지 않도록 이러한 요소의 변화나 불안정성을 방지하기 위해 객체, 하위 시스템 및 시스템에 책임을 할당하는 방법은 무엇일까?
소프트웨어 시스템의 일부 요소(객체, 하위 시스템 등)는 시간이 지나면서 변화하거나(Variations), 또는 외부 환경 요인 때문에 본질적으로 불안정할 수 있다.(= Instability).
만약 시스템의 다른 부분들이 이런 변화하거나 불안정한 요소와 직접적으로 연결되어 있다면, 해당 요소가 바뀔 때마다 연결된 다른 부분들도 함께 수정해야 하거나, 불안정성의 영향을 직접 받게 되어 전체 시스템이 흔들릴 수 있다. (이는 높은 결합도 문제를 야기한다.)
해결책 : 예상되는 변화나 불안정 지점을 식별하고, 이를 중심으로 안정적인 "인터페이스"를 만드는 책임을 할당한다.
- 변화/불안정 지점 식별: 먼저 시스템에서 어떤 부분이 미래에 바뀔 가능성이 높거나, 또는 외부 환경(데이터베이스 종류, 외부 서비스 API, 설정 파일 형식 등) 때문에 불안정할 수 있는지 파악한다.
- 안정적인 '인터페이스' 생성: 파악된 변화/불안정 지점을 둘러싸는 안정적인 경계(Boundary)를 만든다. 이 경계는 주로 '인터페이스(Interface)', '추상 클래스(Abstract Class)', 또는 특정 역할만을 담당하는 '클래스(Class)' 형태가 될 수 있다. 이 안정적인 경계는 내부의 변화나 불안정성을 외부로부터 숨기는 역할을 한다.
- 인터페이스를 통한 상호작용: 시스템의 다른 부분들은 이제 변화하거나 불안정한 요소와 직접 소통하는 대신, 오직 이 안정적인 인터페이스를 통해서만 상호작용 하게 될 것이다.
정리하면, 변경될 여지가 있는 지점에 안정된 인터페이스를 정의하여 사용한다. 인터페이스로 감싸고 다형성(Polymorphism)을 사용하여 이 인터페이스의 다양한 구현을 생성함으로써 다른 요소(객체, 시슽쳄, 서브시스템)에 대한 변화로부터 요소를 보호한다는 것이다.
Protected Variations는 미래의 변화나 현재의 불안정성에 대한 시스템의 취약점을 최소화하기 위해, 변화/불안정 지점 주변에 안정적인 추상화 계층(Abstract Layer)이나 인터페이스를 두는 원칙이다.
예를 들어, 여러분의 프로그램이 데이터베이스에 데이터를 저장해야 한다고 해보자. 나중에 MySQL에서 PostgreSQL로 데이터베이스를 변경할 수도 있고, 아예 다른 종류의 저장 방식(파일, 클라우드 스토리지 등)으로 바뀔 수도 있다. 데이터베이스 연결, 쿼리 실행 등은 이런 변화에 취약한 부분이다.
이때 Protected Variations 원칙을 적용하면, 데이터베이스 접근 로직을 DatabaseGateway와 같은 이름의 인터페이스로 정의하고, 실제 MySQL, PostgreSQL 등에 접근하는 코드는 이 인터페이스를 구현하는 별도의 클래스에 숨긴다. 여러분의 프로그램의 다른 부분(예: 비즈니스 로직)은 직접 MySQL 코드를 호출하는 대신, DatabaseGateway 인터페이스의 메서드만 호출한다.
나중에 데이터베이스를 PostgreSQL로 바꾸더라도, DatabaseGateway 인터페이스를 구현하는 새로운 클래스를 만들고 설정만 변경하면 된다. 프로그램의 비즈니스 로직 코드는 DatabaseGateway 인터페이스를 계속 사용하기 때문에 거의 수정할 필요가 없게 된다. 즉, 비즈니스 로직 코드가 데이터베이스 변경이라는 '변화'로부터 '보호'받는 것이다.
이점 : Flexibility를 높임
핵심요약 :
"언젠가 바뀔 것 같거나 외부 요인 때문에 불안정한 부분이 있다면? 그 부분을 직접 다루지 말고, 변화를 숨겨주는 '안정적인 창구(인터페이스)'를 만들어서 그 창구를 통해서만 소통해라!" 이것이 Protected Variations의 핵심이다. 이 원칙을 잘 적용하면 시스템의 유지보수성, 유연성이 크게 향상될 수 있다.
'개발자의 기록 노트' 카테고리의 다른 글
객체 지향 설계 원칙(OO Design Principles) 등장 배경 (0) | 2025.05.08 |
---|---|
SOLID - R.C Martin의 소프트웨어 설계 원칙 (0) | 2025.05.05 |
마우스 위치 강조하기 (feat. PowerToys) (0) | 2023.06.13 |
ChatGPT : 거짓 정보를 그럴듯하게 포장해내는 인공지능 (0) | 2023.05.13 |
USB 규격 마스터링 (0) | 2023.05.07 |
[Rust] 윈도우 환경에서 컴파일 실패 : linker link.exe not found (2) | 2023.04.05 |