디미터(Demeter) 법칙

디미터 법칙의 탄생 배경

디미터(Demeter)라는 이름의 프로젝트를 진행하던 도중, 다른 객체들과의 협력을 통해 프로그램을 완성해나가는 객체지향 프로그래밍에서 객체들의 협력 경로를 제한하면 결합도를 효과적으로 낮출 수 있다는 사실을 발견했고 디미터 법칙을 만들었다.

현재 디미터 법칙은 객체 간 관계를 설정할 때 객체 간의 결합도를 효과적으로 낮출 수 있는 유용한 지침 중 하나로 꼽히며 객체 지향 생활 체조 원칙 중 ‘한 줄에 점을 하나만 찍는다’로 요약되기도 한다.

디미터 법칙의 핵심

디미터 법칙의 핵심은 객체 구조의 경로를 따라 멀리 떨어져 있는 낯선 객체에 메시지를 보내는 설계는 피하라는 것이다. 바꿔 말해서 객체는 내부적으로 보유하고 있거나 메시지를 통해 확보한 정보만 가지고 의사 결정을 내려야 하고 다른 객체를 탐색해 뭔가를 일어나게 해서는 안 된다.

이러한 핵심적인 내용 때문에 디미터 법칙은 Don’t Talk to Strangers(낯선 이에게 말하지 마라)라고도 불리기도 한다. 또한 한 객체가 알아야 하는 다른 객체를 최소한으로 유지하라는 의미로 Principle of least knowledge(최소 지식 원칙)라고도 불린다.

image

디미터 법칙을 지키기 위한 조건

밑의 (1) ~ (4)에 해당하는 방법만 사용하면 디미터 법칙을 지킬 수 있다.

public class Demeter {
    private Topping cheeseToppping;

    public void goodExamples(Pizza pizza) {
        Foo foo = new Foo();

        // (1) 자신의 객체 내부에 있는 메서드를 호출하는 것
        doSomething();

        // (2) 파라미터로 전달 받은 객체의 메서드를 호출하는 것
        int price = pizza.getPrice();

        // (3) 인스턴스 변수로 가지고 있는 객체의 메서드를 호출하는 것
        cheeseTopping = new CheeseToping();
        float weight = cheeseTopping.getWeightUsed();

        // (4) 새로 생성한 객체의 메서드를 호출하는 것
        Foo foo = new Foo();
        foo.doBar();
    }
    
    private void doSomething() {
        ...
    }

디미터 법칙을 지켜야 하는 이유

  • 디미터 법칙을 어긴 코드
  • 디미터 법칙을 지킨 코드

디미터 법칙을 지키게 되면 의존성이 눈에 띄게 확 줄어든다. 이 덕분에 객체들 간에 협력도를 효과적으로 낮출 수 있는 것이다.

‘디미터 법칙을 어긴 코드’의 의존성

image

‘디미터 법칙을 지킨 코드’의 의존성

image

예제

디미터 법칙을 위반한 경우

public class Participant {
    private final Cards cards;

    public Participant(Cards cards) {
        this.cards = cards;
    }

    public Cards getCards() {
        return cards;
    }
}
public class Cards {
    private final List<Card> cards;

    public Cards(List<Card> cards) {
        this.cards = cards;
    }

    public List<Card> toList() {
        return cards;
    }
}

public class Card {		
}
public class Result {
    public void getNameAndScore(Participant participant) {
        // Participant가 들고 있는 List<Card>를 사용해야 한다고 가정하자.
        List<Card> participantCards = participant.getCards().toList(); // 디미터 법칙 위반

        // ...
    }
}

participant.getCards().toList(); : participant는 파라미터로 정보를 받았기 때문에, participant의 메서드인 getCards()는 사용해도 디미터 법칙을 위반하지 않는다. 하지만 parcipant.getCards()의 return 값이 Cards라는 객체인데, Result는 Cards라는 객체를 내부에 포함하고 있지 않기 때문에, Result 객체 내부에서 Cards의 메서드를 사용하는 것은 디미터 법칙을 위반하게 되는 것이다.

의존성

image

디미터 법칙을 지키도록 개선한 경우

public class Participant {
    private final Cards cards;

    public Participant(Cards cards) {
        this.cards = cards;
    }

		public **List<Card>** getCards() {
        return **cards.toList()**;
    }
}
public class Cards {
    private final List<Card> cards;

    public Cards(List<Card> cards) {
        this.cards = cards;
    }

    public List<Card> toList() {
        return cards;
    }
}

public class Card {		
}
public class Result {
    public void getNameAndScore(Participant participant) {
        // Participant가 들고 있는 List<Card>를 사용해야 한다고 가정하자.
        ~~List<Card> participantCards = participant.getCards().toList(); // 디미터 법칙 위반~~
        **List<Card> participantCards = participant.getCards(); // 디미터 법칙 준수**
        // ...
    }
}

의존성

image

디미터 법칙을 지키지 않을 때는 Result가 Cards에 의존하고 있었다. 하지만 디미터 법칙을 지키자 Result가 Cards에 더 이상 의존하지 않게 되었고, Participant가 Card가 추가적으로 의존하게 되었다. 이로써 Result에 가중되어 있던 의존성이 줄어들게 되고, 전체적으로 의존성이 골고루 나누어져 들어가게 설계가 바뀌었다.

References

디미터 법칙(Law of Demeter)

디미터 법칙 (The Law of Demeter)

Java examples of the Law of Demeter