- 발표 영상 (글을 추천합니다) : https://youtu.be/tdXd-f7QCnE?si=PidTl2ND8x4NxvdK&t=306
디미터 법칙은 객체의 자율성을 높혀 직접 협력하도록 만들어, 객체간 결합도를 낮추고, 응집도를 높히는 법칙이며, 갭슐화를 위한 구체적인 지침이다.
이름은 디비터 법칙이 처음 제안된 프로젝트의 이름인 "Project Demeter"에서 유래되었다.
객체의 자율성이란 무엇일까?
객체간의 결합도와 응집도는 무엇이며,
구체적으로 어떻게 결합도를 낮추고 응집도를 높일 수 있는가?
결론적으로 이것은 왜 필요한가? 어디에 좋은가?
이를 알기 위해 우리는 먼저 객체지향에 대해 생각해보자.
1. 세상과 객체
01010110111...
케이블과 진공관을 활용해 프로그래밍을 하던 시절이 있었다.
시간은 흐르고 흘러, 더 효율적으로 개발할 수 있는 여러 방법이 고안되었다.
낮은 수준의 명령들을 묶어 이름을 붙여주고 (니모닉)
반복되는 동작을 논리적으로 묶거나, 분할해 주었다 (함수)
이로써 우리는 코드 묶음을 재활용 할 수 있게 되었다.
그리고 더 나아가 이런 생각에 도착했다.
"우리가 세상을 바라보듯 코드를 작성하면 어떨까?"
객체지향 패러다임은 인간이 세상을 바라보는 방식대로 코드를 작성할 수 있게 해주었다.
이 세상의 모든 것. 이 글을 읽는 당신과 나, 그 사이의 모니터 화면과, 하늘, 땅, 별, 지구, 바다, 비비큐 황금 올리브 치킨까지..
우리는 '객체'로서 세상을 인식한다.
아주 구체적인 하나의 객체나, 개념 등으로.
그렇지 않은 무언가를 떠올릴 수 있을까?
아마 없을 것이다. 인식할 수 있는 이상 그것은 객체이다.
우리는 치킨을 "씹는다", 아버지와 "대화한다", 지구를 "딛는다"
세상은 수 많은 객체들의 합이며, 수 많은 객체들의 상호작용으로 이루어져있다.
객체만큼 상호작용이 정말로 중요하다.
본디 사전적으론 '실제로 존재하는 것'만을 객체라고 부르지만,
객체지향 패러다임에선 논리와 개념 또한 객체이다.
대표적인 객체지향 언어인 Java는 현실의 객체를 코드로 표현하기 위해 Class 라는 개념을 제공한다.
플라톤은 세상의 존재하는 모든 것들은 "이데아"를 본뜬 것이라고 했다. 그 자체는 세상에 있지도 않고 또 영원히 변하지도 않으면서.. 세상 모든 것을 있게 만드는 어떤 것. 완벽하고 영원 불변한 것. 그것이 이데아이다.
예를 들어 오늘 저녁에 먹었던 비비큐 치킨은 어떤 본질적이고 추상적인 "치킨" 이라는 개념을 본떴다.
오늘 저녁에 먹은 비비큐 치킨은 "닭을 기름에 튀겨낸 요리" 이라는 개념이 부분적으로 들어가 있는 하나의 Practice일 뿐이다. 혹은 "비비큐 치킨"이라는 "비비큐 회사에서 만들어낸 레시피로 튀겨진 치킨"으로 말할 수 있는 본질적인 개념이 있고, 오늘 저녁에 먹은 것은 그 "비비큐 치킨"을 본딴 것이라는 것이다.
자바를 조금이라도 해본 사람이라면, 이는 익숙한 설명이다.
"치킨"이라는 상위 클래스가 있고, 그의 하위 클래스 "비비큐 치킨"이 있으며,
"오늘 저녁에 먹은 치킨"은 "비비큐 치킨"의 인스턴스가 된다!
플라톤의 이데아와 Practice..
플라톤의 제자인 아리스토텔레스는 이를 "Classification"이란 개념으로 정립했고 "Class"라는 용어는 바로 여기에서 나왔다.
세상 만물을 객체("Objcet")로 보기 때문에 모든 자바 클래스의 최상위 클래스의 이름은 "Object"이다.
이렇게 세상 만물을 객체로 보고 코드로 만들려는 노력은
함수 보다도 더 많이 반복을 줄여주었고, 우리는 변화를 만들기 위한 공수가 줄어들었다.
왜일까?
예를 들어, 동물은 산소로 호흡한다.
갑자기 외계인이 침공해 지구의 코드를 바꾸려고 한다. 오늘 부터 모든 동물은 잘소로 숨을 쉽니다. 실시!
기존에 지구에 대한 코드를 작성한 개발자가 코드를 객체지향적으로 짜지 않았더라면, 외계인은 모든 동물 동물 마다 숨쉬는 방법을 확인하고 한땀 한땀 "질소로 숨쉬기"로 바꿔야 한다. 아마도 외계인은 너무 많은 코드를 변경해야 해서 그냥 지구를 멸망시킬지도 모른다...ㅠ
하지만 우리 세상이 객체지향적인 코드로 짜여 있다면, 매우 쉽게 숨쉬는 방법을 바꿀 수 있다.
동물 클래스를 찾아서, 숨 쉴때 사용하는 기체를 질소로 바꾸기만 하면 된다!
객체지향의 주요 목표중 하나는 "변화에 대한 유연함"이다.
변화에 대한 유연함이 왜 필요할까?
좋은 애플리케이션이란
오늘 돌아가고, 내일 변경하기 쉬운 애플리케이션이라고 한다.
애플리케이션은 일단 돌아가야 한다.
말 그대로 프로그램이 그 목적대로, 우리의 기대 대로 잘 동작해야 한다.
그리고 불가피하게, 많은 프로그램들은 사용되면서 계속 변화한다.
새로운 기능이 생기거나, 정책이 바뀌거나, 사용자의 니즈가 바뀌거나..
프로그램 외적인 요소의 변화로 프로그램 또한 변화를 겪게 된다.
이때, 위에서 동물과 산소, 질소의 예시를 들었던 것 처럼 객체지향을 활용하면 변경을 좀 더 쉽게 할 수 있다.
변경하기 쉽다는 것은..
이해하기 쉽고, 쉽게 예측 가능하며, 관련성이 높은 코드들이 모여있고, 어떤 코드를 변경할 때 영향을 받는 부분이 적다는 것... 등등 여러 의미를 포함한다.
덕분에 변경하기 쉽고, 또 확장하기가 쉽다.
객체지향 적인 코드를 "잘" 짠다면,
관련성이 높은 코드들이 모여있고, 어떤 코드를 변경할 때 영향을 적게 받는다.. 를 달성할 수 있다.
이를 그럴싸한 용어로 표현하면, 객체간 응집도가 높고, 결합도가 낮은 구조를 만들 수 있다.
그런 객체지향적인 코드는 어떻게 짤까?
이번엔 너무 원론적인 이야기는 피하고 싶다.
(원론적인 이야기 "캡슐화, 상속, 추상화, 다형성 기능을 적절히 활용해서~~ 클래스는 단일 책임을 갖게 하고, 인터페이스는 적절히 잘 분리하며, 추상화에 의존해 개방 폐쇄 원칙 지키고, 하위 타입은 상위 타입을 대체할 수 있게 만들고, 스프링 같은 프레임워크 도움을 받아 의존성을 역전하고, 거기에 스프링 같은 프레임 워크 도움 받아서 구현체 주입 받아라~~" )
이번엔 인터페이스의 품질을 높히는 구체적인 방법들을 배워보자!
객체의 상호작용과 자율성, 결합도와 응집도 그리고 디미터 법칙과.
인터페이스에 의도를 드러내는 방법, CQRS 등에 대해 알아보자.
2. 객체와 상호작용
세상이 객체들의 상호작용 이루어지듯, 객체 지향적인 애플리케이션은 코드로 표현된 객체들간의 상호작용을 통해 구성된다. 좋은 객체지향 설계란 객체 사이의 의존성을 적절하게 관리한 설계라고 하는데, 의존성이 뭐길래 그리 중요한걸까? 왜 적절하게 관리해야 할까?
객체는 상호작용 속에서 서로를 "의존"하게 된다. 우리는 부모님께 의존하고, 공기에, 물에, 치킨에 의존한다.
객체지향 패러다임에서의 의존은 무언가에 의지한다는 의미 보다는, "사용"한다고 생각하는 것이 좋다.
의존하는 것 자체가 나쁜 것은 절대 아니다. 다만, 의존성이 너무 높은 경우 문제가 된다.
공기와 물, 치킨을 생각해보자. 당신은 공기와 물, 치킨 없이 살 수 있는가?
공기와 물 없이는 절대 살 수 없다! 반면, 치킨은 없어도 (정말 괴롭겠지만) 살 수 있다.
이는 우리가 공기와 물에 대한 의존도가 매우 높고, 치킨과는 그리 높지 않기 때문이다.
앞서 등장했던 전지전능 외계인이 일순간 지구의 모든 산소를 이산화 탄소로 바꾸어 버렸다고 생각해보자!
수 많은 동물들은 금방 죽어버릴 것이다!
객체간의 의존도가 높은 경우, 이렇게 변화에 취약해진다!
공기를 생존의 필수 요소로써 강하게 의존하는 모든 생물은 강제로 영향을 받게 되는 것이다.
세상 모든 치킨을 조약돌 바꾸었다고 생각해보자.
우린 물론 슬프겠지만 소고기나 돼지고기를 먹으면 그만이다.
이는 우리가 치킨을 단순히 "먹을 것들 중 하나"로 약하게 의존하기 때문이다.
(물론 "음식"이라는 개념 그 자체가 변하게 된다면, 매우 큰 영향을 받게 될 것이다.)
이렇게 객체는 서로 상호작용하며, 의존하게 되는데 의존도가 높은 경우 객체는 변화에 큰 영향을 받게 된다.
객체지향 애플리케이션도 똑같다. 서로 상호작용 하며 프로그램을 구성하며, 강하게 의존하는 경우 작은 변화에도 큰 영향을 미치게 된다. 조금 가볍게 말하자면.. 고쳐야 할 부분이 정말 많아지게 된다.
3. "어떻게" 할지는 내가 결정한다 - 객체의 자율성
디미터 법칙은 객체의 자율성을 높혀 의존성을 낮출 수 있다고 했다.
즉, 객체간의 결합도를 낮추고, 응집도를 높힌다고 했다.
객체의 자율성과 의존성은 또 무슨 상관인걸까?
위 그림은 객체의 자율성이 높은 예시이다.
누군가 우리에게 저녁에 뭘 먹었는지 물어봤을때 당신은 "어떻게" 대답할 수 있는가?
아주 자유롭게 대답할 수 있다!
사실대로 알려줘도 되고, 안 알려줘도 된다. 춤으로 알려줘도 되고, 심지어 거짓말을 해도 상관 없다!
자율성이 높은 경우 의존도가 낮다!
어떤 객체가 다른 객체에게 무언가를 요구할 때, "어떻게" 해오던지 상관없다면 의존도가 아주 낮아진다.
그러니까 자율성이 높다면, 요청을 받는 쪽의 메서드 내부 구현이 어떻게 처리되던지 간에, (어떻게 바뀌던 간에)
요청을 하는 쪽의 "내가 원하는 무언가를 돌려 줄거라는 기대"만을 충족시켜줄 수 있다면 상관 없다는 것이다.
(위의 예시에서는 오늘 저녁에 먹은 음식을 답변해줄 것이라는 기대를 품고 있다)
조금 헷갈린다면 반대로 자율성이 낮은 상황을 살펴 보자.
똑같이 상대방은 내가 저녁에 먹은 음식이 궁금한 상황이다.
단, 나의 자율성이 아주 아주 아주 낮은 상황이라고 생각해보자.
상대방은 나의 핸드폰을 뺐어간 다음 갤러리나 결제 내역을 확인할 수도 있고, 강제로 내 배를 갈라 확인할 수도 있다.
아니면 궁예의 관심법을 이용해 내 생각을 열어 볼 수도 있다.
그러니까 요청에 대해 "어떻게" 행동할지를 '나'라는 객체가 스스로 결정한 것이 아닌 상대방이 결정해버린 것이다.
나를 존중해주지 않고, 내 자율성을 존중해 주지 않고, 억지로 나에게서 정보를 가져간 것이다!
자연스럽게 캡슐화가 무너졌다!
객체는 내부를 훤히 드러냈으며, 너무 깊숙히 의존하고 있다!
즉, 변경에 많은 영향을 받는다!
예를 들어, (물론 일어날 수 없는 일이지만) 상대방이 내 배를 열어 먹을 음식을 확인하는 코드를 만들었다고 생각해보자.
전지전능 외계인이 나타나.. 내 신체구조를 뒤죽박죽 바꾸어 놨다고 생각해보자. 이 경우 상대방은 코드를 수정해야 한다.
하지만, 나에게 단순히 물어만 봤다면, 신체 구조가 어떻게 바뀌었든 상대방은 똑같이 "치킨 먹었어" 라는 답변을 받아볼 수 있다.
코드로 표현하자면 아래와 같은 상황이다
(정말 과장한 코드다. 예시이므로 Optional을 그냥 get했다.)
메서드 `getJinhoLastEatenFood()`가 jinho라는 객체의 자율성을 인정하지 않고, 내부 필드들을 마구 꺼내어 쓰고 있다.
body를 꺼내고, 소화기관을 꺼내고, 위를 찾아 내용물을 반환한다..
객체의 내부 구현이 외부로 훤히 노출되었다. 즉, 캡슐화가 무너진 상황이다.
Jinho의 내부 표현이 바뀌거나, Organ, Stomach의 내부 구현이 바뀌는 경우, 위 메서드의 많은 부분을 수정해야 할 수도 있다.
위와 같은 코드는 높은 결합도와 낮은 응집도를 가졌다고 할 수 있다!
4. Low Coupling, High Cohension Pattern
높은 결합도와 낮은 응집도를 가진 코드는 뭐가 문제일까?
결합도란 객체끼리 상호 의존하는 정도를 의미한다.
의존성이 너무 큰 경우 객체간의 결합도가 높다고 표현하는데,
앞서 언급한 것과 같이 의존성이 너무 높으면 하나의 객체의 변화에 대해 다른 객체들이 많은 영향을 받게 된다.
(인간과 물과 공기의 예시)
결합도를 낮추면 변경에 용이하고, 재사용성이 높아진다. ("어떻게" 해오던지 관심 없어서, 여기저기 재사용 가능)
응집도란 객체가 자신의 책임을 흩어지지 않게 하여 가지고 있는 정도를 의미한다.
다른 객체들의 책임을 떠안을 수록 응집도가 낮아지게 되고, 변경에 취약해진다.
요컨대, 자신의 일은 스스로 하자는 것이다.
예를 들어 레스토랑의 주방장이 홀 서버와 청소 당담자에게 직접 일을 시키는 역할까지 겸으로 맡고 있다고 생각해보자.
아주 아주 구체적으로 도구와 방법까지 지시한다고 생각해보자.
만약 서버와 청소 당담자의 내부 구성이나 업무 수행 방식이 바뀌는 경우 주방장도 계속 변화를 겪게 된다.
서버와 청소 담당자가 자신의 역할을 자기가 맡고 있다면, 주방장이 쓸대없이 바뀔 필요는 없어진다.
이 Low Coupling, High Cohension Pattern은 코드를 짤 때 구체적인 하나의 선택 지표로도 활용할 수 있다.
여러 선택지가 있을 때 결합도가 낮고, 응집도가 낮은 코드를 짤 수 있다면 변경에 유연한 코드를 짤 수 있다는 것이다.
결론적으로 객체들의 결합도를 낮추고, 응집도를 높히면 의존성이 낮아지고, 변경에 용이해진다!
우리는 이것을 목표로 해야한다.
앞서 보여준 코드는 분명히 결합도가 높고 응집도가 낮은 상황이다..
우리는 이러한 목표를 디미터 법칙을 통해 이뤄낼 수 있다.
4. Don't Talk to Stranger
위에서 보인 나쁜 예시의 코드를 그대로 가져왔다.
어떻게 객체의 자율성을 높혀줄 수 있을까?
아! Getter를 쓰면 되나요?
Getter를 써서 get 메서드가 반환할 내용은 객체가 자유롭게 결정하도록 하는 것이죠!
어느 정도는 맞지만, 위와 같이 코드를 Getter를 이용한 버전으로 바꿔도 문제 상황은 똑같다.
여전히 객체 내부 구현이 훤히 드러나 있다!
과장하기 위해 한 줄로 만들었는데,
이렇게 객체의 필드나 메서드에 접근하기 위한 도트 (.)이 기차처럼 줄줄이 이어진 코드를 Train Wreck Code라고 부른다.
(기차 충돌 코드)
디미터 법칙이 원하는 것은 단순히 Getter를 사용는 것을 넘어 객체의 자율성을 존중해,
상호작용에 스스로 참여하도록 만드는 것이다.
바로 Jinho라는 객체가 스스로 `getJinhoLastEatenFood()` 메서드에 참여하게 만드는 것이다.
디미터 법칙에선 Train Wreck 대신 내부를 숨기고 부끄럼을 타는 Shy Code를 작성하라고 한다.
코드적으로는 객체에 파고 드는 점(.)을 하나로 제한할 것을 권한다! (이제야 디미터 법칙이 나왔다)
예를 들어 아래와 같이 코드를 작성하라는 것이다.
위의 코드는 단순히 Jinho 객체에게 마지막으로 먹은 음식을 알려줄 것을 요청하고 있다.
"마지막으로 먹은 음식 가져오기" 책임을 Jinho 객체에게 자율적으로 수행할 수 있게 하는 것이다!
메서드 `getJinhoLastEatenFood()`는 단지 Jinho에게 마지막으로 먹은 음식의 출력만을 요청하고,
"어떻게" 처리해 오는지에 대해선 관심을 끄는 것이다!
그럼 객체는 자유로운 방법으로 자신의 내부에서 처리하여 답을 주는데,
덕분에 Jinho 객체의 내부 구현이 어떻게 바뀌더라도 결국 Food 객체를 반환하기만 한다면 메서드는 변경될 필요가 없어지는 것이다! (단지 그 Food가 Jinho가 실제로 마지막으로 먹은 음식이기를 기대하면서!)
디미터 법칙은 객체의 자율성을 높히라고 한다.
그리고, 너무 먼 객체와 대화하지 말고, 가까운 객체와만 대화하라고 한다. (상호작용)
코드적으로는 객체에 파고드는 토트(.)을 하나로 제한하라고 조언한다!
이러한 원직을 어머니가 자식에게 수상한 사람과는 대화하지 말라고 가르치듯
Don't Talk to Stranger! 원칙이라고 부른다.
4.1 가까운 객체의 기준이란 무엇일까?
노출 범위를 제한하기 위해, 객체의 모든 메서드는 다음에 해당하는 객체와 메서드만 호출하길 권한다.
아래의 객체와 메서드들이 "가깝다"라고 할 수 있다.
1. Class 자체를 포함한 Method의 인자로 전달된 클래스
2. Method에 의해 생성된 객체로 전달된 클래스
3. Method이 호출하는 메서드에 의해 생성된 객체로 전달된 클래스
4. 전역 변수로 선언된 객체로 전달된 클래스
5. Class의 인스턴스 변수의 클래스
조금 어지럽다.. Java 코드상에선 어떤지 보면 좀 더 이해가 쉽다.
1. this 객체와 속성
2. this의 속성인 컬렉션의 요소
3. 메서드의 매개변수들
4. 메서드 내에서 생성된 지역 객체
도움이 됐을까? 위의 객체와 메서드들과만 소통하면 되는데,
조금 비약이 있을 수 있지만 결국 객체에 파고드는 점이 하나 이하라면, 위의 조건들에 전부 만족하는 객체와만 소통할 수 있다.
4.2 알겠어!! 점을 하나로 줄이면 되지?
디미터 법칙을 단순히 "도트 한개만 찍기"로 오해할 수도 있다.
앞서 계속 설명한 것과 같이 디미터 법칙은 단순히 "도트 하나"를 강조한 법칙이 아니라,
객체 내부에 대해 얼마나 물어보느냐가 주요 관심사인 법칙이다.
예를 들어 위와 같은 스트림 코드는 전부 IntStream이라는 동일 클래스 인스턴스를 반환하고,
객체 내부에 대해 묻지도 않는다.
이런 코드까지 전부 없애라는 것이 아님을 이해해야 한다. 디미터 법칙을 어긴 코드가 아니다!
(Train Wreck처럼 보이기 위해 일부러 줄을 바꾸지 않았다)
5. Tell. Don't Ask Style
디미터 법칙을 준수한 코드 스타일을
"묻지 말고 시켜라" - Tell. Don't Ask 스타일이라고도 부른다.
그러니까 Jinho 객체를 열고 열고 열고 열어서 마지막으로 먹은 음식을 얻지말고, (객체의 내부를 묻지 말고)
그냥 시키라는 것이다. "Jinho야. 니가 마지막으로 먹은 음식을 줘!" 라고 말이다.
결국 "어떻게"를 알려 하지 말고 "무엇을" 원하는지에만 집중하라는 것이다.
특히, 상태를 기반으로 상태를 변경할 필요가 있을 때, 위 스타일을 고려해야한다.
아픈 여자친구를 떠올려 보자.여자친구의 상태는 아픈 상태이므로, 남자친구인 나는 여자친구가 빨리 회복되길 바란다.그래서 현관 문 앞에 따끈한 전복죽을 놓고 집에 가버렸다.
상태를 기반으로 다른 상태를 변경한 것이다.그랬더니 현관문을 연 여자친구는 속으로 생각했다."아... 아플땐 삼첩분식 바질 치즈 크림 떡볶이에 분모자 추가해서 먹으면 싹 났는데;;"
저녁 메뉴의 선택은 여자친구의 몪이다.
상대방의 상태를 확인하고, 내가 직접 상대방의 상태를 조작하지 마라.
그냥 스스로에게 맡겨라!
무뚝뚝한 남자친구로 보일 수 있지만, "배고플텐데 저녁 먹어" 라고 "시키는" 것이 사실은 여자친구에게 더 행복한 일일 수도 있었다.
상대방의 상태를 확인하고, 직접 조작하는 일은 캡슐화를 깨는 일이며, 절차지향적인 코드에 가깝다.
그렇다고 항~~상 묻지 말라는 것은 아니다.
가끔은 물어야 한다.
객체가 단순한 자료구조인 경우 어쩔 것인가?
그냥 묻는게 맞다. Tell Don't Ask는 항상 지킬 수 있는 스타일은 아니다.
6. 결국 캡슐화, 캡슐화
결국 앞서 언급한 디미터 법칙이나 묻지말고 시켜라 스타일은 결국 캡슐화를 다른 관점에서 표현한 것이다!
이들은 캡슐화에 대한 구체적인 지침이라고 할 수 있다!
말은 많았지만 결론적으로는 객체와의 상호작용 속에서 가능하다면 상대방이 맡은 책임은 상대방이 수행할 수 있도록 만들고, 내부적으로 어떻게 수행되는지에 대해선 묻지도 따지지도 말라는 것이다!
단지 원하는 것을 요청하면 "알아서" 수행한 다음 돌려줄 것이라는 인식 정도만 가지라는 것이다.
객체를 스스로 책임지는 자율적인 존재로 만들라는 것이다!
내부적으로 어떻게 수행되는지 관심을 갖지 않아도 된다는 것은, 상대가 어떻게 해와도 상관 없다는 것이다.
잘 만든 인터페이스가 있다면 우리는 구체적인 구현을 뒤로 미룰 수 있다!
예를 들어 Reposiotry에 접근할 수 있는 인터페이스를 아주 잘 만들어 놓으면,
우리는 설계 과정에서 구제적으로 어떤 DB를 사용할지 미리 결정하지 않아도 된다.
단지 필요에 따라 나중에 구현체만 바꾸어 주면 되기 때문이다.
이런 자유로움은 관심사의 분리를 돕고, Testbility를 높혀주기도 한다.
이런 설계를 우리가 구현할 때는 보통 추상화와 의존성 역전, 그리고 의존성 주입 등을 활용해 구현해왔다.
이 글에서 그런 추상화를 "잘" 하는 것과 캡슐화의 중요성을 얻을 수 있으면 좋겠다.
7. 디미터 법칙과 객체지향 설계는 무적일까??
아니다. 설계에서 진짜 "법칙"이란건 없다!
어떤 상황에서도 무조건 옳은 법칙은 거의 없으며,
모든 설계는 결국 트레이드 오프다. 얻는 것이 있다면 잃는 것이 있다.
유일한 법칙이 있다면 "경우에 따라 다르다" 라는 법칙이다. (전설의 사바사)
디미터 법칙을 적용했더니 오히려 응집도가 낮아지거나 결합도가 높아지는 경우도 있을 수 있다.
PeriodCondition 은 영화 티켓 할인 정책을 표현한 클래스로,
할인 여부를 판단하는 메서드 isSatisfiedBy() 를 통해 Screening을 전달 받으면,
내부의 3가지 필드와 Screening의 WhenScreened 객체가 가진 정보를 비교하여 할인 여부를 판단한다.
영화 상영 시간이 할인 요일이고, 시작 시간 끝 시간이 할인 시간대에 들어가 있다면 할인 조건을 만족한다는 의미로 true를 반환한다.
위 코드를 본 당신은 화가 치밀어 오를 수도 있다. isSatisfiedBy() 메서드를 보자.
PeriodCondition 클래스가 아주 잔인하게 Screening의 자율성을 무시하며 배를 갈라 필드들을 마구잡이로 사용해버린다. 접근을 위한 도트(.) 또한 필드마다 두개씩 있다. 내부적으로 WhenScreened 이라는 객체가 있을 것이라는 것도 훤히 드러난다.
즉, Screening과의 의존도가 너무 높다!
이에 참지 못하고 디미터 법칙을 적용해봤다.
이제 PeriodCondition는 할인 여부에 대한 조건들만을 dayOfWeek, startTime, endTime 필드로 가지고 있고,
할인 여부는 Screening 스스로가 판단할 수 있게끔 필드들을 넘겨주기만 하면 된다.
이제 Screening이 isDiscountable() 메서드를 통해, 할인 여부를 남이 아닌 자신이 판단할 수 있어 행복해진걸까?
아니다.
위 변화에 의해 두 가지 단점이 발생했다.
1. Screening이 기간에 따른 할인 여부 판단 책임을 떠안게 됐다.
2. 기간과 관련된 구체적인 변수들을 인자로 받게 되었다.
1번으로 인해 결국 Screening의 응집도가 낮아졌다.
왜냐하면 원래 Screening은 할인 여부 판단에 대한 책임이 없는 객체이기 때문이다.
자신이 원래 맡지 않았던 책임까지 지게 되면서 오히려 응집도가 떨어지게 되었다!
그리고 2번으로 인해 결합도가 높아졌다.
기간과 관련된 구체적인 변수들이 인자로 들어오게 되었는데, 이제 인자로 들어오게 되는 변수들과 강하게 결합되고 말았다. DayOfWeek, LocalTime 등의 변화가 생기거나, PeriodCondition이 기간을 표현하는 방식이 변하게 된다면, 이제 Screening까지 그 영향을 받게 되는 것이다.
즉, Screening의 입장에서 오히려 다른 객체와의 결합도가 높아졌다!
디미터 법칙을 적용했더니, 오히려 적용 목표와 정 반대의 효과가 발생해버렸다.
앞에서 그렇게 장점들을 말하더니, 이런 결과가 나와버렸다.
혼란스럽다.
그럼 우리는 어떨 때 디미터 법칙을 적용하고, 어떨 때는 적용하지 않는 것이 좋을까?
설계에 있어서, 손해와 이득을 따질 수 있는 정말 가장 좋은 방법은 일단 도입해 보는 것이라고 한다.
도입도 전에 많은 요소들을 고려하며 머리 싸메지 말고 일단 도입한다면,
머리로만 생각했을 때 보다 장단점이 훨씬 극명하게 드러난다고 한다!
이 글은 여기서 끝난다.
이 글을 읽으신 분께서 객체의 자율성, 응집도와 결합도
그리고 객체지향과 "설계"에 대해 조금이나마 얻어갈 수 있었길 기대한다.
- 자바 인터페이스와 객체지향적인 인터페이스를 위한 지침들에 대한 글도 써 봤다. -> 링크
- 부끄럽고 조금 부족하지만 발표 영상도 있다. -> 영상
Reference
- 오브젝트 <조영호>
- 객체지향 사실과 오해 <조영호>
- 자바 객체지향의 원리와 이해 <김종민>
- Java/Spring 주니어 개발자를 위한 오답노트 <김우근>
- 자바의 정석 <남궁성>
- [OOP] 디미터의 법칙(Law of Demeter) - 망나니 개발자
- 디미터 법칙(Law of Demeter) - Tecoble
- 디미터 법칙(Law of Demeter)과 묻지 말고 시켜라(Tell, Don’t Ask) - Hello, Hannah!
- 사진 자료 래퍼런스 : { "죽 사진" : "본죽", "치킨 사진" : "비비큐", "떡볶이" : "삼첩분식", "지구" : "위키 백과", "애니악" : "위키 백과", "사람 사진" : "GDSC Hongik", "알약 사진" : "나무 위키", "외계인" : "토이스토리", "데메테르 석상" : "The British Museum" }