✌️ Chapter 4: 유연한 인터페이스 만들기
- 애플리케이션은 클래스로 구성되어 있지만 메시지를 통해 정의된다. 그렇기 때문에 디자인은 객체 사이를 가로지르는 메시지에 관심을 두어야 한다.
- 객체 사이의 소통은 인터페이스를 통해 이루어진다.
📚 인터페이스 정의하기
클래스는 하나의 책임을 제대로 수행하기 위해 존재하며 수많은 메서드를 구현하고 있다.
🎈 퍼블릭 인터페이스
클래스의 퍼블릭 인터페이스를 구성하는 메서드는 바깥 세상을 향한 클래스의 얼굴이다. 이런 메서드는 다음과 같은 특징이 있다.
- 클래스의 핵심 책임을 드러낸다.
- 다른 객체에 의해 호출될 수 있다.
- 쉽게 변경되지 않는다.
- 다른 객체가 안정적으로 의존할 수 있다.
- 테스트를 통해 꼼꼼하게 문서화되어 있다.
🎈 프라이빗 인터페이스
그 외의 메서드는 프라이빗 인터페이스의 한 부분이다.
- 세부적인 구현을 담당한다.
- 다른 객체에 의해 호출되지 않는다.
- 필요에 따라 언제든 변경할 수 있다.
- 다른 객체가 의존하기에는 위험하다.
- 테스트에서 다루지 않을 수도 있다.
🎈 책임, 의존성 그리고 인터페이스
클래스의 퍼블릭한 부분은 안정적인 부분이다. 프라이빗한 부분은 변경될 수 있는 부분이다.
메서드를 퍼블릭인지 프라이빗인지 규정할 때 우리는 이 클래스 사용자에게 어떤 메서드에 의존하는 것이 보다 안정적인지 알려주는 것이다.
📚 퍼블릭 인터페이스 찾아내기
좋은 퍼블릭 인터페이스는 예상치 못했던 변화에 따라오는 비용을 줄어주는 반면 안 좋은 퍼블릭 인터페이스는 그 비용을 증대시킨다.
🎈 예시 애플리케이션: 자전거 여행 회사
요구사항으로는 여행객은, 여행길을 선택하기 위해서, 정해진 날짜에, 자신에게 맞는 난이도의, 자전거를 빌릴 수 있는, 여행길 목록을 보고싶어 한다.
🎈 의도를 구성하기
이 애플리케이션에 들어갈 법한 클래스가 어떤 것이 있는지를 떠올린다. (Customer, Trip, Route, Bike, Mechanic 클래스 정도 있다.)
이런 클래스를 바로 떠올릴 수 있었던 이유는 이 클래스들이 애플리케이션 속의 명사들, 정보(data)와 행동(behavior) 둘 다를 가지고 있는 명사들을 표현하기 때문이다. 이것들을 도메인 객체(Domain Objects)라고 부른다. 이 도메인 객체들은 명시적이다.
하지만 도메인 객체제 집중하는 것이 아니라 도메인 객체들이 주고받는 메시지에 주목해야 한다. 이 메시지는 새로운 객체를 찾도록 도와준다.
🎈 시퀸스 다이어그램 사용하기
시퀸스 다이어그램은 통합 모델링 언어(UML, Unified Modeling Language)에 정의되어 있으며 UML이 제공하는 다이어그램 중 하나이다.
시퀸스 다이어그램은 객체들의 배치와 메시지 전송 전략에 대해 검토해 볼 수 있는 간단한 방법을 제공한다.
시퀸스 다이어그램은 객체와 객체 사이를 오가는 메시지를 보여준다. 메시지는 가로선으로, 메시지 선은 화살표로 처리되어 있는데 이 화살표의 끝에 수신자가 있다. 객체가 전송받는 메시지를 처리하는 동안 객체는 활성화되어 있어 그동안 객체의 세로선은 좌우로 넓어져 세로로 긴 직사각형꼴을 띠게 한다.
시퀸스 다이어그램은 객체들이 주고받는 메시지를 명시적으로 드러내고 객체들은 퍼블릭 인터페이스를 통해서만 소통하기 때문에 시퀸스 다이어그램은 인터페이스를 나타내고, 실험해보고 결국은 인터페이스를 정의하기 위한 도구가 된다.
시퀸스 다이어그램을 사용함으로써 디자인에 대한 논의의 방향이 바뀌었다. 이전에는 클래스에 대해, 그리고 클래스가 누구를/무엇을 아는지 논의에 주목했지만 이제는 메시지 중심으로 디자인을 이야기한다.
객체를 가지고 있기 때문에 메시지를 보내는 것이 아니라, 메시지를 전송하기 때문에 객체를 갖게 된 것이다.
🎈 어떻게(How)해야 하는지 말해주지 말고, 어떤 것(What)을 달라고 요구하기
송신자가 원하는 것을 요구하는 메시지와 수신자가 어떻게 행동 해야하는지 알려주는 메시지. 이 두 메시지를 구분하는 것은 매우 중요한데 둘 사이의 차이를 이해하는 것이야말로, 잘 정의된 퍼블릭 인터페이스를 가진, 재사용이 가능한 클래스를 만들기 위한 핵심 포인트이다.
어떻게와 관련된 모든 책임은 Mechanic에게 모두 넘어간다. 그렇기 때문에 나중에 Mechanic을 보완하더라도 Trip는 언제나 원하는 결과를 얻을 수 있게 되었다.
어떻게에서 어떤 것으로 바뀌었을 때 그 부수적인 효과 중 하나는 Mechanic에 포함된 퍼블릭 인터페이스의 양이 눈에 띄게 줄었든다. 작은 퍼블릭 인터페이스를 갖는다는 것은 다른 객체가 의존할 수 있는 메서드의 수가 적다는 사실을 뜻한다.
🎈 주어진 맥락에서 독립적일 수 있게 하기
객체를 필요로 하는 맥락은 객체의 재사용성에 바로 영향을 미치는데 복잡한 맥락 속에 위치한 객체는 사용하기도 어렵고 테스트하기도 어렵다.
가장 최고의 상황은 객체가 자신의 맥락으로부터 완전히 독립되어 있는 것이다. 다른 객체가 누구인지, 그들이 무엇을 원하는지 전혀 모른 채로 협업할 수 있는 객체는 재사용될 수 있다.
이 문제에 대한 해결책은 어떤 것과 어떻게 사이의 구분에 놓여 있고, 해결책을 찾기 위해서는 원하는 것이 무엇인지를 살펴봐야 한다.
이 원하는 어떤 것은 준비되어야 하고 이 무엇인가를 준비하는 것은 온전히 자신의 책임이다. 그렇기 때문에 자신이 원하는 것만 말하고 자기 자신을 인자로 넘겨준다.
🎈 다른 객체를 믿기
이렇게 독립적인 객체지향 스타일로 짜면 메시지를 보내는 송신자는 알지도 못하고 신경 쓰지도 않는다. 그리고 무슨 일을 하는지도 전혀 모르고 있다. 그저 메시지를 보낼 객체만 있으면 된다. 이 메시지를 수신하는 객체가 적절히 행동하리라 믿고 있을 뿐이다.
이러한 맹목적인 믿음이 객체지향 디자인의 핵심이다. 이 믿음 덕분에 객체들은 맥락에 얽매이지 않고 협업할 수 있다.
🎈 메시지 기반 애플리케이션 만들기
시퀸스 다디어그램은 유용하지만, 그저 하나의 도구일 뿐 그 이상이 아니다.
이 도구는 우리가 메시지에 집중할 수 있도록 도와주고 첫 번째로 통과시켜야 하는 테스트의 합리적인 의도를 형성할 수 있게 해준다.
우리의 관심을 객체에서 메시지로 옮기면서 퍼블릭 인터페이스를 기반으로 애플리케이션을 디자인하는 데 집중할 수 있게 되었다.
📚 자신의 인터페이스를 드러내는 코드 작성하기
잘 정의된 인터페이스가 있다는 것이 중요한 것이지, 인터페이스가 완벽해야 하는 것이 중요한 것이 아니다.
명확한 의도를 가지고 구현해야 한다.
🎈 명시적인 인터페이스 만들기
새로운 클래스를 만들 때마다 그 클래스의 인터페이스를 선언한다. 퍼블릭 인터페이스의 메서드는 다음과 같아야 한다.
- 일반적인 의미, 엄밀하고 명시적으로 규정되어 있어야 한다.
- 어떻게보다는 어떤 것에 대해 말해야 한다.
- 예측할 수 있는 한도에서나마 바뀌지 않을 이름을 지어야 한다.
- 추가적인 인자를 해시로 받아라.
루비는 다음과 같은 세 가지 키워드로 public
, protected
, private
를 제공한다. 첫째로, 키워드는 메서드가 안정적인지 아닌지 알려준다. 둘째로, 애플리케이션의 다른 객체들에게 해당 메서드가 얼마나 노출되는지 결정한다.
private
키워드는 가장 불안정하고 눈에 띄지 않는 종류의 메서드이다.
protected
키워드도 불안정한 메서드를 나타내는데 그 노출 정도에서private
키워드와 다르다.protected
메서드는 수신자가self
이거나 같은 클래스의 인스턴스 또는self
의 클래스의 인스턴스일 경우에 한해서 명시적인 수신자를 지정할 수 있다.
private
와 protected
키워드는 강력한 제약조건이라기보다는 부드러운 방어막 같은 것이다. 그렇기 때문에 이 키워드를 이용해서 메서드에 접근하지 못하도록 막을 수 있다는 생각은 잘못된 것이다. 이 키워드은 접근을 막지 않는다. 단지 조금 더 번거롭게 만들 뿐이다.
🎈 다른 이의 퍼블릭 인터페이스를 존중하다.
다른 클래스와 협업할 때는 그 클래스의 퍼블릭 인터페이스만 사용하도록 노력해야 한다.
우리의 코드가 프라이빗 인터페이스에 의존하고 있다면 변화에 영향을 받을 위험이 증가한다.
🎈 프라이빗 인터페이스에 의존할 때는 특별히 주의를 기울어야 한다.
아무리 노력해도 프라이빗 인터페이스에 의존할 수밖에 없는 순간이 있는데 이 경우 3장에서 설명한 기술을 사용해 의존성을 고립시켜야 한다.
어쩔 수 없이 프라이빗 메서드를 사용하더라도 이 메서드를 애플리케이션 이곳저곳에서 사용하지 않도록 할 수 있다.
🎈 최소한의 맥락 속에 위치시키기
송신자가 원하는 것을 클래스에 요청할 때 우리가 작성한 클래스의 구현과 작동방식에 대해 전혀 몰라도 되는 퍼블릭 메서드를 작성해야 한다.
퍼블릭 인터페이스는 꼭 만들고 이를 통해 클래스를 둘러싼 맥락이 줄어들고 다시 사용하기 쉬워지며 테스트하기도 편해진다.
📚 데메테르의 원칙
데메테르의 원칙(LoD)은 객체들의 결합도를 낮추면서 코딩할 수 있도록 해주는 규칙이다.
🎈 데메테르 원칙 정의
이 원칙은 메서드가 메시지를 전송하지 말 것을 요구한다. 메시지를 전달받은 객체가 바로 이어 다른 타입의 객체에게 메시지를 전달하는 것을 금지한다.
# 이 메시지 연쇄는 우리가 데메테르의 원칙을 위반하고 있다는 사실을 보여준다.
customer.bicycle.wheel.tire
customer.bicycle.wheel.rotate
hash.keys.sort.join(', ')
🎈 위반의 결과
코드가 지녀야 하는 가치로 투명성, 적절성, 사용가능성, 모범성을 제시했다. 위 코드는 이 TRUE를 비추어 보았을 때도 부적절하다. (92Page 참고)
메시지 연쇄를 통해 접근한 어트리뷰트의 값을 변경하지 않는다는 전제 아래서만 이런 접근은 허용된다. 중간에 여러 객체를 끼고 있는 메시지 연쇄를 통해 멀리 있는 행동에 접근하고 있는 것이다.
세 번째 메시지 연쇄는 데메테르 원칙을 위반하고 있는 것이 전혀 아니다. 중간에 낀 객체들의 타입을 체크하면서 코드를 평가해보면 중간에 낀 모든 객체들은 같은 타입을 반환하고 있다.
🎈 데메테르의 원칙을 위반하지 않기
이런 코드를 피하기 위한 방법 중 하나는 위임을 사용하는 방법이다. 객체지향에서 메시지를 위임한다는 뜻은 메시지를 다른 객체에게 넘긴다는 것이다. (루비엔 delegate.rb
와 forwardable.rb
를 가지고 있다.)
🎈 데메테르의 원칙에 귀 기울이기
데메테르의 원칙에 귀를 기울인다는 것은 우리의 관점을 다시 검토해 보는 것으로 메시지 기반의 관점을 취하면 새로운 메시지를 발견할 수 있고 이 메시지가 곧 퍼블릭 인터페이스가 된다. 그리고 이 퍼블릭 인터페이스를 정의할 새로운 객체를 발견할 수 있다.
반면 도메인 객체의 족쇄를 벗어버리지 못하면 이 객체들의 퍼블릭 인터페이스를 이용해 기다란 메시지 연쇄를 작성하게 될 것이다.