티스토리 뷰

이번 글은 같이 스터디를 진행한 친구의 글을 옮긴 것이다. 원글은 여기에서 볼 수 있다.

올바른 책임 할당을 위한 GRASP패턴


GRASP패턴은 “General Responsibility Assignment Softwarre Pattern(일반적인 책임 할당을 위한 소프트웨어 패턴)”의 약자로 객체에게 책임을 할당할 때 지침으로 삼을 수 있는 원칙들의 집합을 패턴 형식으로 정리한 것이다.

1. 도메인 개념에서 출발하기

  • 설계를 시작하기 전에 도메인에 대한 개략적인 모습을 그려 보는 것이 유용
  • 도메인 개념이란 책임 할당의 대상으로 사용하는 거대한 틀
  • 설계를 시작하는 단계에서는 개념들의 의미와 관계가 정확하거나 완벽할 필요가 없다.
  • 중요한 것은 완벽한 설계가 아닌 설계를 시작하는 것!

2. 정보 전문가(INFORMATION EXPERT) 패턴

INFORMATION EXPERT 패턴 즉, 정보 전문가 패턴은 객체란 상태와 행동을 함께 가지는 단위라는 객체지향의 가장 기본적인 원리를 책임 할당의 관점에서 표현한 것이다.

  • 객체란 상태와 행동을 통합한 캡슐화의 단위
  • 객체의 책임과 책임을 수행하는 데 필요한 상태는 동일한 객체 안에 존재해야 한다.
  • 객체가 자신이 소유하고 있는 정보와 관련된 작업을 수행한다.
  • 정보와 행동을 최대한 가까운 곳에 위치시키기 때문에 캡슐화를 유지할 수 있다.

3. 낮은 결합도(LOW COUPLING) 패턴

  • 변화의 영향을 줄이며 재사용성을 증가시키는 설계 패턴
  • 설계의 결정을 평가할 때 적용할 수 있는 평가 원리.
  • 불필요한 협력을 없애는 방향으로 설계를 결정해야 한다.
  • 현재의 책임 할당을 검토하거나 여러 설계 대안들이 있을 때 낮은 결합도를 유지할 수 있는 설계를 선택하라.

4. 높은 응집도(HIGH COHESION) 패턴

  • 복잡성을 관리할 수 있는 설계 패턴, 설계를 평가할 때 적용할 수 있는 평가 원리이다.
  • 클래스가 하나 이상의 이유로 변경돼야 한다면 변경의 이유를 기준으로 클래스를 분리하라.
  • 클래스의 속성(인스턴스)을 초기화하는 시점에 속성의 일부만 초기화 한다면, 함께 초기화 되는 인스턴스를 기준으로 코드를 분리하라.
  • 메서드들이 사용하는 속성에 따라 그룹이 나뉜다면, 이들 그룹을 기준으로 클래스를 분리하라.
  • 현재의 책임 할당을 검토하거나 여러 설계 대안들이 있을 때 높은 응집도를 유지할 수 있는 설계를 선택하라.

5. 창조자(CREATOR) 패턴

객체를 생성할 책임을 어떤 객체에게 할당할지에 대한 지침. 어떠한 객체 A의 창조자 객체 B가 있다고 가정해 보자.

  • B가 A객체를 포함하거나 참조한다.
  • B가 A객체를 기록한다.
  • B가 A객체를 긴밀하게 사용한다.
  • B가 A객체를 초기화하는 데 필요한 데이터를 가지고 있다. (이 경우 B는 A에 대한 정보 전문가다)

즉, CREATOR 패턴의 의도는 어떤 방식으로든 생성되는 객체와 연결되거나 관련될 필요가 있는 객체에 해당 객체를 생성할 책임을 맡기는 것.

6. 다형성(POLYMORPHISM) 패턴

  • 객체의 역할의 개념에 해당한다. 역할이란 협력 안에서 이루어 질 수 있는 대체 가능성을 의미.
  • 역할을 사용하면 객체의 구체적인 타입을 추상적으로 표현 할 수 있다.
  • 객체의 타입에 따라 변하는 행동이 있다면 타입을 분리하고 변화하는 행동을 각 타입의 책임으로 할당하라.
  • 조건적인 논리(if ~ else, switch ~ case) 대신 다형성을 이용하면 새로운 변화를 다루기 쉽게 확장에 열려 있다고 말할 수 있다.

7. 변경 보호(PROTECTED VARIATIONS) 패턴

  • 변경을 캡슐화 하도록 책임을 할당하는 것
  • 변화가 예상되는 불안정한 지점들을 식별하고 그 주위에 안정된 인터페이스를 형성하도록 책임을 할당하라.

 

상속대신 합성으로!


GRASP패턴의 6번과 7번을 보면 우리가 자주 접한 개념이 등장한다. 바로 객체지향 개발 5대원칙인 SOLID의 O에 해당하는 OCP( : 변경에는 닫혀있고 확장에는 열려있어라!)이다.

예를 들어, 영화에 설정된 할인 정책을 실행 중에 변경할 수 있어야 한다는 요구사항이 추가됐다고 가정해 보자. 만약 영화객체가 할인 정책을 구현하기 위해 Movie라는 추상 클래스를 상속하는 각각의 DiscountMovie 객체를 만들도록 설계 했다면, 실행 중인 영화의 할인 정책을 변경하기 위해 새로운 인스턴스를 생성한 후 필요한 정보를 복사해야 할 것이다. 이것은 번거로울 뿐만 아니라 오류가 발생하기 쉽고 쓸데없는 자원의 낭비를 야기한다. 이를 해결하려면 어떻게 해야할까?

바로 Movie객체를 상속받는 DiscountMovie객체를 생성하는 것이 아닌 Discount라는 추상화된 인터페이스를 상속받는 각각의 Discount구현체들을 생성 후(POLYMORPHISM) Movie에 의존성을 주입(DIP : 하위 모듈에 발생하는 변경이 상위 모듈에 변경을 요구하는 위계관계를 끊는 의존의 역전) 하면 될 것이다. 이처럼 상속을 통한 구현이 아닌 추상화를 통한 합성으로 더욱 유연한 코드를 얻을 수 있다.

 

현실적인 대안


책임 주도 설계에 익숙해지기 위해서는 많은 노력과 시간이 필요하다. 그렇기에 아직 책임 주도 설계에 익숙해 지지 않은 초급 개발자들이 바로 프로젝트에 적용하기에는 현실적인 어려움이 많다. 본 책에서는 이러한 현실적인 문제들을 인지 하고 있고 그것을 해결하기 위한 대안으로 리팩토링을 제시 하고 있다.

우선 작성하라!

책임 주도 설계의 경험이 적은 개발자의 경우, 아무것도 없는 상태에서 책임과 협력에 관해 고민하기 보다는 최대한 빠르게 목적한 기능을 수행하는 코드를 작성해 일단 실행되는 코드를 얻고 난 후에 코드 상에 명확하게 드러나는 책임들을 올바른 위치로 이동시키는 리팩토링을 실시하는 것이 효과 적일 수 있다.

메서드 응집도

우선 작성하기에 따라 목적한 기능을 수행하는 코드를 얻었다면 응집도는 낮고, 결합도는 높은 거대한 몬스터 메서드(monster method)들이 클래스 안에 구현되어 있을 확률이 높다. 지금부터 할 일은 이 몬스터 메서드들을 적절한 책임을 가진 여러 개의 메서드로 분리하는 것이다. 클래스가 작고, 목적이 명확한 메서드들로 구성돼 있다면 변경을 위해 수정되어야 하는 부분을 판단하기 쉬워지고, 재사용성 또한 높아진다. 또 작은 메서드들이 마치 하나의 주석과 같은 효과를 주어 코드를 이해하기도 쉬워진다.

객체를 자율적으로 만들자

몬스터 메서드에서 적절한 책임을 가진 여러 개의 메서드로 바꾸었다면, 이제 객체를 나눌 차례이다. 어떤 메서드를 어떤 클래스로 이동시켜야 할까? 메서드가 사용하는 데이터를 저장하고 있는 클래스로 메서드를 이동시키면 된다. 객체란 자신이 소유하고 있는 데이터를 스스로 처리하여야 한다는 것을 기억하자!

이러한 방식으로 데이터 중심으로 구현한 후 점차 리팩토링해 나가는 연습을 한다면 캡슐화, 결합도, 응집도를 이해하고 유연하고 깔끔한 코드를 얻을 수 있을 것이다.

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday