[OOP] 디미터 법칙에 대한 오해와 디미터 법칙에 대해

객체지향 프로그래밍을 잘 하기 위한 9가지 원칙을 준수해가며 코드를 리팩토링 하는 과정에서 디미터 법칙에 대한 오해가 한 가지 생겼습니다.

Enum 클래스의 경우 열거형 상수 값을 가져 올 때

GameMessage.START.getMessage()

이런 식으로 코드 한줄에 점(.)이 2번 들어가게됩니다.

바로 여기서 오해가 생기게 되었습니다.

디미터 법칙은 코드 한줄에 점(.)을 하나만 허용해야 한다고 되어 있는데, "점(.)이 2번 들어갔으니 이건 디미터 법칙 위반아니야 ?" 라는 오해가 생겼는데요.

이 오해에 대해 정리한 제 생각을 공유해보자고 합니다.


디미터 법칙이란?

디미터 법칙은 결합도가 낮은 설계를 위해 생긴 규칙이지, 점(.) 이 몇번 쓰였냐가 중요한게 아니라 객체지향 측면에서 바라봐야한다고 생각합니다.
여기서 객체지향 측면에서 바라본다는 말은 디미터 법칙과 관련된 객체지향 원칙 중 “캡슐화”를 위반하느냐 아니냐 라고 할 수 있습니다.
즉, 객체의 내부 구조에 대해 외부에서 직접적으로 접근하는 것을 피하고, 오직 객체에게 직접적으로 연결된 친구들에게만 메세지를 보내야 한다는 것이 이 디미터 법칙의 핵심이라고 생각합니다.

 

디미터 법칙을 위반하는 경우

쉬운 이해를 돕기 위해 한가지 예를 들어 보겠습니다.

public class Person {
    private final Wallet wallet;

    public int getWallet() {
        return wallet.getAmount();
    }
}

public class Wallet {
    private final Money money;

    public int getMoney() {
        return money.getAmount();
    }
}

public class Money {
    private final int amount;

    public int getAmount() {
        return amount;
    }
}

 

Person이라는 클래스를 가지고 있고, 이 Person이 Wallet이라는 객체를 가지고, 또  Wallet은 Money라는 객체를 가지고 있다고 가정해봅시다. 
이제 Person 이 얼마나 많은 돈을 가지고 있는지 알고 싶다면 어떻게 해야 할까요?

아래 처럼 작성하게 될 것입니다.  이는 디미터 법칙을 위반합니다.

int amount = person.getWallet().getMoney().getAmount();

 

왜냐하면 person이 갖고 있는 wallet 인스턴스 변수에 접근하고, 이 wallet 객체 안에 있는 money에 접근하고

또, 이 money 객체 안에 있는 getAmount()메서드를 호출했기 때문입니다.
위 코드는 Person의 내부 구조(Wallet과 Money)에 대해 너무 많이 알고 있습니다.
즉, 캡슐화를 위반할 뿐만 아니라, 결합도가 높아져 Wallet이나 Money의 구조가 바뀌면 추가적인 수정이 필요해집니다.


디미터 법칙을 위반하지 않도록 바꿔보자

그렇다면 위 구조를 디미터 법칙을 위반하지 않도록 하려면 어떻게 바꾸면 될까요?

public class Person {
    private final Wallet wallet;

    public int getAmount() {
        return wallet.getAmount();
    }
}

public class Wallet {
    private final Money money;

    public int getAmount() {
        return money.getAmount();
    }
}

public class Money {
    private final int amount;

    public int getAmount() {
        return amount;
    }
}

 

이런식으로 person.getAmount();를 사용해 Person 객체가 내부적으로 Wallet과 Money 객체에 메시지를 보내서 필요한 정보를 가져오도록 할 수 있습니다.

int amount = person.getAmount();

 

이렇게 하면 Person 객체를 사용하는 클라이언트 코드는 Wallet과 Money의 내부 구조에 대해 알 필요가 없습니다. 
디미터 법칙을 준수하면서 Person 객체의 내부 구조를 캡슐화하여 코드의 결합도를 낮추고 유지보수성을 높일 수 있습니다.


디미터 법칙이 허용되는 예시

처음으로 돌아가서 Enum클래스에서 상수값을 가져오는 코드를 봅시다.

GameMessage.START.getMessage()

 

이 코드는 GameMessage 라는 Enum클래스가 직접 알고 있는 START 상수를 getMessage를 꺼내오기 때문에 디미터 법칙을 위반하지 않습니다.
객체 자신이 직접적으로 알고(갖고) 있는 객체와만 상호 작용하기 때문이죠.

다른 예시로 정적 팩토리 메서드를 봅시다.

Item.newInstance().buy()

 

이 코드도 디미터 법칙을 위반하지 않습니다.
Item.newInstance()로 생성된 인스턴스는 Item 객체 자신이며 이 객체가 직접적으로 알고(갖고) 있는 buy() 메서드를 호출 했기 때문입니다.