5장 책임 할당하기

swucs·2022년 2월 8일
0

오브젝트

목록 보기
3/13

출처 : 오브젝트 (조영호 저)

GRASP 패턴을 이해하고 나면 응집도와 결합도, 캡슐화 같은 다양한 기준에 따라 책임을 할당하고 결과를 트레이드오프할 수 있는 기준을 알게 된다.

책임 중심의 설계로 전환하기 위해서는 다음의 두 가지 원칙을 따라야 한다.

  • 데이터보다 행동을 먼저 결정하라
  • 협력이라는 문맥 안에서 책임을 결정하라

협력을 시작하는 주체는 메시지 전송자이기 때문에 협력에 적합한 책임이란 메시지 수신자가 아니라 메시지 전송자에게 적합한 책임을 의미한다. 협력이라는 문맥에서 적절한 책임이란 곧 클라이언트 관점에서 적절한 책임을 의미한다. 올바른 객체지향 설계는 클라이언트가 전송할 메시지를 결정한 후에야 비로소 객체의 상태를 저장하는 데 필요한 내부 데이터에 관해 고민하기 시작한다.
책임 주도 설계의 핵심은 책임을 결정한 후에 책임을 수행할 객체를 결정하는 것이다.

GRASP (General Responsibility Assignment Software Pattern)

일반적인 책임 할당을 위한 소프트웨어 패턴

책임 주도 설계방식의 첫 단계는 애플리케이션이 제공해야 하는 기능을 애플리케이션 책임으로 생각하는 것이다. 이 책임을 애플리케이션에 대해 전송된 메시지로 간주하고 이 메시지를 책임질 첫 번째 객체를 선택하는 것으로 설계를 시작한다.

“메시지를 전송할 객체는 무엇을 원하는가?”

메시지를 결정했다면 메시지에 적합한 객체를 선택해야 한다.

“메시지를 수신할 적합한 객체는 누구인가?”

객체에게 책임을 할당하는 첫번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것이다. GRASP에서는 이를 INFORMATION EXPERT(정보 전문가) 패턴이라고 부른다. 이 패턴을 따르는 것만으로도 자율성이 높은 객체들로 구성된 협력 공동체를 구축할 가능성이 높아지는 것이다.

책임을 할당할 수 있는 다양한 대안들이 존재한다면 응집도와 결합도의 측면에서 더 나은 대안을 선택하는 것이 좋다. 다시 말해 두 협력 패턴 중에서 높은 응집도와 낮은 결합도를 얻을 수 있는 설계를 선택해야 한다.

CREATOR패턴

객체 A를 생성해야 할 때 아래 조건을 최대한 많이 만족하는 B에게 객체 생성 책임을 할당하라

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

이미 결합돼 있는 객체에게 생성 책임을 할당하는 것은 전체적인 결합도에 영향을 미치지 않는다. 결과적으로 CREATOR 패턴은 이미 존재하는 객체 사이의 관계를 이용하기 때문에 낮은 결합도를 유지할 수 있게 한다.

클래스 분리

하나 이상의 변경이유를 가지는 객체는 응집도가 낮고 서로 연관성이 없는 기능이나 데이터가 하나의 클래스 안에 뭉쳐져 있다는 것을 의미한다. 따라서 변경의 이유에 따라 클래스를 분리해야 한다.

응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화 한다. 반면 응집도가 낮은 클래스는 객체의 속성 중 일부만 초기화 하고 일부는 초기화 되지 않는 상태로 남겨진다. 따라서 함께 초기화되는 속성을 기준으로 코드를 분리해야 한다.

또한 메서드들이 인스턴스 변수를 사용하는 방식을 살펴보고, 모든 메서드가 객체의 모든 속성을 사용한다면 클래스의 응집도는 높다고 볼 수 있다. 반면 메서드들이 사용하는 속성에 따라 그룹이 나뉜다면 클래스의 응집도가 낮다고 볼 수 있다. 이 경우 응집도를 높이기 위해서 속성 그룹과 해당 그룹에 접근하는 메서드 그룹을 기준으로 코드를 분리해야 한다.

클래스 응집도 판단하기

  • 클래스가 하나 이상의 이유로 변경돼야 한다면 응집도가 낮은 것이다. 변경의 이유를 기준으로 클래스를 분리하라
  • 클래스의 인스턴스를 초기화하는 시점에 경우에 따라 서로 다른 속성들을 초기화하고 있다면 응집도가 낮은 것이므로 초기화되는 속성의 그룹을 기준으로 클래스를 분리하라
  • 메서드 그룹이 속성 그룹을 사용하는지 여부로 나뉜다면 응집도가 낮은 것이다. 이들 그룹을 기준으로 클래스를 분리하라

POLYMORPHISM 패턴

객체의 암시적인 타입에 따라 행동을 분기해야 한다면 암시적인 타입을 명시적인 클래스로 정의하고 행동을 나눔으로써 응집도 문제를 해결할 수 있다. 다시 말해 객체의 타입에 따라 변하는 행동이 있다면 타입을 분리하고 변화하는 행동을 각 타입의 책임으로 할당하라는 것이다. 이를 다형성 패턴이라 한다.

조건에 따른 변화는 프로그램의 기본 노리다. 프로그램을 if ~ else 또는 switch ~ case 등의 조건 논리를 사용해서 설계한다면 새로운 변화가 일어난 경우 조건 논리를 수정해야 한다. 이것은 프로그램을 수정하기 어렵고 변경에 취약하게 만든다.

다형성 패턴은 객체의 타입을 검사해서 타입에 따라 여러 대안들을 수행하는 조건적인 논리를 사용하지 말라고 경고한다. 대신 다형성을 이용해 새로운 변화를 다루기 쉽게 확장하라고 권고한다.

PROTECTED VARIATIONS 패턴

변화가 예상되는 불안정한 지점들을 식별하고 그 주위에 안정된 인터페이스를 형성하도록 책임을 할당하도록 하는 패턴.
”설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화하라 [GOF94]”라는 오랜 격언이 PROTECTED VARIATIONS 패턴의 본질을 잘 설명해준다.

클래스를 변경에 따라 분리하고 인터페이스를 이용해 캡슐화하는 것은 설계의 결합도와 응집도를 향상시키는 매우 강력한 방법이다.

데이터 중심의 설계는 데이터와 관련된 클래스의 내부 구현이 인터페이스에 여과 없이 노출되기 때문에 캡슐화를 지키기 어렵다. 따라서 책임을 중심으로 설계해야 한다. 객체에게 중요한 것은 상태가 아니라 행동이다.

책임 주도 설계의 대안

만약 적절한 책임과 객체를 선택하는 일이 어렵다면 최대한 빠르게 목적한 기능을 수행하는 코드를 작성하고 그 이후에 코드 상에 명확하게 드러나는 책임들을 올바른 위치로 이동시키는 방법을 사용한다. 주의할 점은 코드를 수정한 후에 겉으로 드러나는 동작이 바뀌어서는 안된다는 것이다. (리팩터링)

트랜잭션 스크립트의 문제점

  • 어떤 일을 수행하는지 한눈에 파악하기 어렵기 때문에 코드를 전체적으로 이해하는데 너무 많은 시간이 걸린다.
  • 하나의 메서드 안에서 너무 많은 작업을 처리하기 때문에 변경이 필요할 때 수정해야 할 부분을 찾기 어렵다.
  • 메서드 내부의 일부 로직만 수정하더라도 메서드의 나머지 부분에서 버그가 발생할 확률이 높다.
  • 로직의 일부만 재사용하는 것이 불가능하다.
  • 코드를 재사용하는 유일한 방법은 원하는 코드를 복사해서 붙여넣는 것뿐이므로 코드 중복을 초래하기 쉽다.

한마디로 응집도가 낮고 이해하기도, 재사용하기도 어렵고 변경하기도 어렵다. 이런 메서드를 몬스터 메서드(monster method)라고 부른다.

우선 처음에는 메소드를 작게 나누는 것부터 시작하게 되면, public 주요 메서드는 어떤 일을 하는지 한눈에 알아 볼 수 있는 메서드가 된다. 각 메서드는 단 하나의 이유에 의해서만 변경된다.

그러나 메서드의 응집도 자체는 높아지지만 클래스의 응집도는 여전히 낮다. 클래스의 응집도를 높이기 위해서는 변경의 이유가 다른 메서드들을 적절하게 클래스로 분배해야 한다.

메서드가 사용하는 데이터를 저장하고 있는 클래스로 메서드를 이동시키면 된다.

profile
백엔드 개발자

0개의 댓글

관련 채용 정보