본문 바로가기
개발

디미터의 법칙(Law of Demeter)이란 뭘까? 그리고 왜 지켜야 할까?

by 손너잘 2021. 2. 18.

클린코드를 읽으면서 디미터의 법칙이라는 것을 처음 접했었다.

당시에는 그냥 읽으면서, 기차충돌(Message Chains) 코드를 피하면 좋다는 건가? 라는 식으로 이해하고 넘어갔다.

왜 지켜야 하는지에 대한 진지한 고민을 못해본 것 이다.

 

최근 최범균님의 개발자가 반드시 정복해야 할 객체지향과 디자인 패턴 을 읽고 디미터의 법칙에 대한 인사이트를 얻었다.

일단, 디미터의 법칙이 뭔지부터 알아보자.

 

위키피디아에 의하면 아래와 같이 디미터의 법칙을 설명하고 있다.

 

...
In its general form, the LoD is a specific case of 
loose coupling.
...
  • Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
  • Each unit should only talk to its friends; don't talk to strangers.
  • Only talk to your immediate friends.

 

일단, 디미터의 법칙은 결합도를 낮춘다고 말하고 있다. 또한 간단하게 3줄로 디미터의 법칙을 요약하면 다음과 같다고 말하고 있다.

unit을 객체로 해석하는게 더 이해하기 편해 객체로 표현하도록 하겠다.

 

  • 각각의 객체 다른 객체에 대해 최소한의 지식을 가지고 있어야 한다. : 현재 객체과 근접한 객체만 있어야 한다.
  • 각각의 객체는 자신의 친구하고만 이야기 해야 한다. : 낮선이와 이야기 하지 마!
  • 가까운 친구와만 이야기 해라!

 

사실 이 3가지만 읽으면 "그래서 어쩌라고?" 가 가장 먼저 떠오를 것이다. 필자도 그랬었다.

그러면 위의 3가지 원칙을 잠시 접어두고, 객체지향적 관점에서 위 원칙을 한번 서술해 보겠다. 객체지향 관점에서 디미터의 법칙을 지키기 위해서는 아래의 법칙을 지키면 된다고 한다.

 

  • 객체 자신을 호출한다.
  • 메소드의 파라메터로 들어온 외부 객체의 메소드만 호출한다.
  • 메서드에서 생성한 객체의 메서드만을 호출한다. (파라메터로 받은 객체의 메서드에서 반환되는 객체를 의미하는게 아니다!, 비즈니스 로직에서 new 로 생성한 객체를 의미하는 것이다!)
  • 객체의 필드로 존재하는 객체만을 호출한다.

 

처음 서술했던 법칙과 매칭을 시켜보면, 정말 위 법칙은 객체와 밀접한 관계(친구)에 있는 객체만을 사용하도록 정의한다. 그렇다면 이 법칙을 왜 지켜야 하는걸까?

 

이는 캡슐화 와 관련이 있다.

 

우리는 코딩을 하면서 아래와 같은 코드를 작성한 경험이 꽤 있을거라 생각한다.

public int getAmount(User user) {
	return user.getWallet().getAmount().getValue();
}

간단한 코드다. 하지만 이 코드는 디미터의 원칙을 위반했다. 왜인가? 기차충돌 코드를 작성해서일까?.

맞다. 기차충돌 코드를 작성해서라고 말할 수도 있다. 하지만 한발짝 더 깊이 생각해 보자. 위 코드를 풀어보면 아래와 같이 표현된다.

public int getAmount(User user) {
    Wallet wallet = user.getWallet();
    Amount amount = wallet.getAmount();
    return amount.getValue();
}

디미터의 법칙에 의하면 서로 밀접한 관련이 있는 객체의 메소드만 호출하도록 정의하였다.

위 메소드를 봤을 때 밀접한 관련을 가지는 객체는 무엇인가? 바로 자기 자신과 User이다(물론 필드는 없다고 가정한다).

하지만 위 코드를 보면 Wallet과 Amount같은, 메소드와 밀접한 관련이 없는 객체의 메소드를 호출하고 있다. 따라서 이는 디미터의 법칙을 위반한 코드이다. 또한, user객체의 구현인 Wallet이 노출됐다. 캡슐화 또한 위반한다.

 

이렇다면 이게 무엇이 문제인가?? 이러한 코드는 객체간의 결합도를 증가시킨다. 즉, 여러 객체와의 의존관계가 형성되어 버린다. 의존관게가 형성되면 객체의 변경이 일어났을 때 의존성이 있는 다른 객체들이 그 영향을 받아 코드의 수정이 일어날 수 있다.

 

만일 위의 코드에서 wallet의 코드가 수정되어 getAmount() 메소드명이 getCurrentAmount()로 변경되었다고 가정해 보자. 그러면 user로 부터 amount를 받아오는 로직이 들어간 모든 클래스가 수정될 것이다.

이러한 현상은 OCP를 위반하며 객체지향의 특징을 제대로 활용할 수 없게 되버린다.

 

그렇다면 만일 위 코드가 아래와 같았다면 어땠을까?

class User {
    Wallet wallet;
    ...
    public int getAmount() {
        return wallet.getAmount();
    }
}
-----------------------------------
public int getAmount(User user) {
    int amount = user.getAmount();
    return amount;
}

Wallet의 코드가 getCurrentAmount()로 변경되었어도 user로 부터 amount를 받아오는 로직에는 변경이 없다. 변경이 일어났다면, User의 getAmount()메소드의 로직만 변경되었을 것이다. (사실 메소드명을 바꿔서 줄줄이 바뀌는걸 OCP를 위반했다고 말할 수 있는지는 모르겠으나... 어쨌든 최소화는 할 수 있다.).

 

즉, 코드의 변경에 대한 비용이 더욱 적어진다.

 

위키피디아를 보면 디미터의 법칙은 결합도(coupling)를 낮춰준다고 한다. 위의 예제에 따르면 이는 결국 의존성 관계를 최소화 시킬 수 있다는 의미로 받아드릴수 있다.

 

디미터의 법칙과 같이 객체지향의 법칙에 대한것을 이해하기 위해서는 단순히 그 법칙만을 이해하려 하면 어렵다. 그와 연관된 객체지향의 특징들을 모두 알고 있어야 그때서야 "아 이게 이래서 그런거구나!" 하고 이해할 수 있을것이다.

사실 디미터의 법칙도 파고 들어가면 결국 객체지향의 "메세지를 보내라", "캡슐화", "OCP" 등과 깊은 관련이 있는것을 알 수 있다.

 

필자가 이 글을 작성한 지금, 디미터의 법칙을 왜 지켜야 하는지 깨닫는데는 객체지향에 대한 많은 학습이 수반되었다. 디미터 법칙에 대한 글을 작성하며, 독자가 객체지향에 대한 선행 학습이 없는 상태라고 가정하니 정말 어디까지 설명을 이어가야 할지 모르겠어서 일단은 어느정도는 알고있다는 가정하에 글을 작성해 버렸다.

 

따라서 글이 이해가 안되는 독자도 있을것이라 생각한다... 이 점에 있어서는 깊은 사과를 표하고 싶다.

'개발' 카테고리의 다른 글

PDF 번역을 쉽게 하자! Make Sentence for Translator  (6) 2021.01.21

댓글