kok202
오브젝트 디자인 스타일 가이드 (1/2) 서비스와 객체

2022. 6. 6. 18:09[공부] 독서/오브젝트 디자인 스타일 가이드

http://www.yes24.com/Product/Goods/91167539

 

오브젝트 디자인 스타일 가이드 - YES24

잘 작성한 객체지향 코드는 읽고 변경하고 디버그하기 즐겁다. 이 책에서 보여주는 객체 디자인에 대한 보편적 모범 사례를 익혀 코딩 스타일을 향상하자. 이 명확한 규칙은 어떤 객체지향 언어

www.yes24.com

코드 레벨에 대한 설명이 많아 이는 생략하고 보편적인 가치 위주로 정리합니다.

(😛) 은 사견입니다.

 

의존성

객체 A가 일을 처리하는데 객체 B를 필요하면 A가 B에 의존한다고 한다. A가 B에 의존하는 방식은 여러가지다.

  1. A에서 B 인스턴스를 만들어서 의존할 경우
  2. 알고 있는 위치에서 B 인스턴스를 가져와서 의존할 경우
  3. 생성할 때 주입한 B의 인스턴스에 의존할 경우

의존성을 가져오는 것이 Service location (서비스 위치 지정)이며, 생성자 인자를 통해 의존성을 얻는 것이 의존성 주입이라고 이해해도 충분하다.

 

Composition

한 객체를 다른 객체의 속성에 할당하는 것을 객체 컴포지션 (object composition)이라고 한다.

 

객체의 두 종류

  1. 작업을 수행하거나 정보를 반환하는 서비스 객체 
  2. 데이터를 담고 있으며 데이터를 조작하거나 가져오는 행위를 선택적으로 드러내는 객체

 

서비스란 무엇인가?

작업을 수행, 정보를 반환하며, 생명 주기가 단순하고, 한번 생성으로 영원히 일을 하는 객체.

 

서비스의 불변성

⭐⭐⭐ 서비스는 불변해야한다.

서비스의 인스턴스를 만든 후에는 행위가 바뀌어선 안된다. 서비스가 인스턴스화 된 이후 의존성 주입이 되면 서비스의 행위를 바꾸게 만든다. 이는 서비스를 예측 불가능하게 만드는 요소중 하나다. (😛: setter 자체가 서비스에 있으면 안된다는 의미)

 

서비스에 의존성 주입

생성자 주입으로 의존성을 주입해야한다.

생성자 주입으로 의존성을 주입할 때는 필요한 것의 위치가 아니라 필요한 것 자체를 주입해야한다.

 

서비스 위치 지정자 (service locator)

위에서 말한 필요한 것을 들고 있는 객체. 어떤 의미에서 서비스 위치 지정자는 올바른 키를 알고 잇으면 서비스를 가져올 수 있는 맵과 같다. 서비스 위치 지정자는 프로그램안의 사용가능한 모든 서비스에 접근할 수 잇게 해주기 때문에, 서비스 위치 지정자를 이용한 생성자 주입으로 처리하려는 유혹에 빠질 수 있다. 하지만 그렇게 되면 코드에서 추가적인 함수 호출을 하게되 서비스(혹은 어떤 컴포넌트든 상관없음)가 실제 어떤 일을 하는지가 모호해진다. 추가로 서비스 위치 지정자를 주입 받은 서비스 입장에서는 필요한 데이터에 어떻게 접근해서 가져와야하는지 알아야할 필요도 생긴다. 따라서 필요한 것 자체를 주입하는 것이 좋다.

서비스 위치 조정자를 부르는 데 다음과 같은 용어들이 사용된다.

  • Service locator
  • 관리자 (manager)
  • 레지스트리
  • 컨테이너

 

생성자 주입에 사용되는 파라미터는 Optional 이면 안된다.

Optional 이 코드를 불필요하게 복잡하게 만든다. 설정 값을 필수 인자로 두면서 합리적인 기본값도 같이 제공하고 싶다면 Configuration 을 전달하는 방법도 있다. 하지만 그래도 필요한 의존성을 완전히 드러내는 것이 좋다.

 

생성자 주입만 사용한다.

객체를 불완전한 상태로 생성할 수 없어야한다.

서비스는 불변이어야 한다. 인스턴스 생성을 마친 후에는 바꿀 수 없어야한다.

(😛 생성자 주입을 사용해야하는 이유 중 하나로 멤버 변수를 final로 만들 수 있다라는 글이 많다. 항상 그런 글들을 읽다보면, final 로 만든게 대체 뭐가 좋은데? 라는 의문이 남았다. 책에서 해답을 알려주는데, 서비스에 사용되는 멤버 변수를 final 로 만들 수 있는게 중요한 것이 아니다. 서비스 자체가 불변하는 협력 객체를 의미하기 때문에 final 로 되어있어야 서비스라고 할 수 있는 것이다.)

 

생성자에서는 속성을 할당하는 일만 한다.

생성자에 속성 할당 외 다른 책임이 주어지면 할당에 규칙이 생기고 이것이 서비스를 복잡하게 만드는 요인이 된다.

(😛 생성자에 요구되는 책임은 assert, assign 두 개인 듯.)

 

정적 의존성을 사용하지 않는다.

 

시스템 호출 (특히 시간)을 명백히 한다.

시간과 관련한 의존성을 드러내지 않으면, 런타임에 상관없이 해당 코드는 항상 실제 현재 시각을 사용하여 동작할 것이다. 그렇게 되면 테스트 코드가 있을 때 테스트를 언제 수행하는지에 따라 테스트 결과가 달라진다. 이는 테스트를 불안정하게 만들고 특정 시점 이후에 테스트를 수행하면 실패하게 되는 원인이 된다.

(😛: Time.getCurrentTimestamp() 같은 코드를 만들어선 안되는 이유)

 

생성자 주입시 할 수 있는 방법.

필요한 것을 주입한다 >>>>> 필요한 것의 위치를 주입한다 >> | 절대하면 안되는 것: Optional 주입

 

객체

객체는 항상 일관성 있는 상태로 존재하도록 하는 것이 좋다. 예를들어 Position 이라는 객체가 있을 때 setX, setY 가 있으면 일관성을 해친다. x, y 가 중요한 객체라면 x, y 입력없이는 생성을 못하도록 하는 것이 좋다. 도메인의 불변성을 보호해야한다.

 

생성자에 의미있는 데이터를 요구해야한다.

예를들어 Positon 에서 x 가 [-200, 200] 안의 값만 허용한다하면, 이를 검증하는 로직이 생성자에 있어야한다.

(😛 종종 주석으로 경고 메시지가 담긴 코드를 볼 수 있는데, 주석만 달지말고 유호하지 않은 값이 들어오면 예외를 던지고, 예외를 던지는지 확인하는 테스트 코드를 작성하는 것이 낫다.)

 

생성자 테스트

생성자는 실패해야하는 것만 테스트한다.

(😛 생성자 테스트는 예외를 던지는지만 테스트하는 것이 좋다. 생성자에 값이 제대로 할당 됬는지 알기 위해 getter 가 무분별하게 만들어지는 것을 유도한다.)

 

Named constructor

정적 팩토리 메소드를 명명한 생성자(named constructor)라고 한다.

명명한 생성자의 이름은 create 같은 어휘보다 도메인에 더 적합한 이름이 있다면 이를 사용하는 것이 좋다.

객체를 생성하기 위한 도메인 별 동의어를 도입하는 데 사용할 수 있다.

 

새로운 객체 추출

의존성을 주입할 때 항상 같이 다니는 객체가 있다면 새로운 객체로 추출해서 나타낸다.

ex. Amount + Currency -> Money

 

서비스를 메소드 인자로 전달해도 되는가?

서비스를 메소드 인자로 전달하는 것은 다소 이상할 수 있다. 다른 구현 방법을 고려하는 것도 합리적이다.

어떤 사례에서는 서비스를 메소드 인자로 전달해야 한다는 점이, 그 행위 대신 서비스를 구현해야한다는 암시일 수 있다라고도 본다.

 

VO, DTO, Entity

가급적이면 어땐 객체를 만들려할 때 VO > Entity > DTO 순으로 만들 것을 고민하는 것이 좋다.

 

VO

불변 객체

값이 변경되어야할 일이 있다면 변경하는 대신 새로운 객체로 교체한다.

(😛 VO 의 변경자는 새로운 VO 를 반환해야한다.)

 

DTO 

프로퍼티가 public 으로 선언되도 되는 객체.

(😛 이건 솔직히 잘모르겠다. 아무리 DTO 라고 해도 멤버 변수를 public 으로 만드는게 맞는걸까?)

(😛 만약 lombok 을 사용해 @Getter, @Setter 를 무분별하게 달았다면 이는 이미 DTO 일 수 있다.)

 

Entity (개체)

  • 변경을 추적할 수 있다. (개체 안에 어떤 변경 명령이 들어왔는지 추적할 수 있도록 이벤트를 기록할 수 있다.) 
  • 식별 가능하다.
  • 응용 프로그램의 도메인 개념이다.
  • 수명 주기가 있다.
  • 영속성을 갖는다.

(😛 항상 Entity 관련 글을 읽어보면 식별 가능한 클래스라고해서 Id 가 있으면 Entity 다라고 주장하는 글을 많이 봤는데, 그럴때마다 드는 의문이 "그럼 VO 나 DTO 가 id 를 들고 잇으면 그것도 Entity 로 봐야하는가?" 였다. Entity 에서 중요한 것은 식별 가능하다는 것보다 명령이 가능한 데이터인가? 가 좀 더 맞아보인다.)

 

Entity 구현의 원칙

  • 개체 상태를 변경하는 메소드의 반환 타입은 void 이고 이름은 명령형이어야한다.
  • 메소드 호출로 개체의 상태가 유효하지 않게 되지 않도록 보장해야한다.
  • 개체 내부에서 일어나는 일을 테스트 하기 위해 내부를 모두 노출해서는 안된다. 대신 변경 기록을 유지하고 기록을 노출해서 객체가 변경된 내용과 바뀐 이유를 알 수 있어야한다.