Table of contents

원칙7

정말 친한 친구하고만 얘기하라.(최소 지식 원칙) (Principle of Least Knowledge) = (Law of Demeter)

일곱번째 원칙은 퍼사드 패턴에서 등장했다. 객체 사이의 상호작용, 즉 어떤 객체들이 다른 객체를 사용할 때는 아주 가까운 친구사이에서만 허용하는 것이 좋다는 원칙이다.

클라이언트는 여러개의 벙커를 소유하고 있고, 그 벙커는 다시 여러개의 마린과 파이어벳을 소유하고 있다. 이런 상황에서 클라이언트가 벙커의 모든 마린들이 공격하도록 명령하는 callAttackMarines() 를 살펴보자.

public void callAttackMarines() {
    Iterator<Bunker> bkIter = bunkerList.iterator();
    while(bkIter.hasNext) {
        Bunker bk = bkIter.next();
        List<Marine> mrList = bk.getMarines();
        Iterator<Marine> mrIter = mrList.iterator();
        while(mrIter.hasNext()) {
        	mrIter.next().attack();
        }
    }
}

클라이언트의 callAttackMarines() 메소드는 벙커의 메소드를 호출하여 Marine 목록을 가져온다. 그런 다음 다시 Marine의 attack()메소드를 호출했다. 이 때, 클라이언트와 친한 친구는 직접 관계를 맺는 List, Bunker뿐이다. Bunker로부터 얻어온 Marine은 클라이언트와 친하지 않음에도 불구하고 Marine과 메시지를 주고 받는다. 이러한 상황을 클래스들이 복잡하게 얽혀있다고 한다. 복잡하게 얽혀있는 시스템의 한 부분을 변경하면 다른 부분까지 줄줄이 고쳐야될 수도 있다. 또한, 관리하기가 어렵고 남들이 보기에도 이해하기 어려운 코드가 되고 만다.

만약, 마린의 attack()메소드를 클라이언트뿐 아니라 벙커도 사용할 수 있다면 attack()의 메소드를 변경하거나 할 때 벙커와 클라이언트를 모두 고쳐야하는 상황이 된다. 아예 Bunker에서만 호출할 수 있게하고, 클라이언트는 간접적으로 Bunker의 callAttackMarines()를 호출하도록 하면 attack()메소드가 변경되었을 때 Bunker에서만 고쳐주면 된다.

책에서는 최소지식원칙을 지키기위한 네가지 가이드라인을 제시한다.

  1. 객체 자체의 메소드
  2. 메소드에 매개변수로 전달된 객체
  3. 그 메소드에서 생성하거나 인스턴스를 만든 객체
  4. 그 객체에 속하는 구성요소

객체 자체의 메소드에서 2, 3의 원칙을 지키지 않는다면 당연히 이 원칙에도 위반된다. 객체 자체의 메소드에서도 메소드 내부에서 생성되거나 매개변수로 만든 객체의 메소드만 호출하거나 구성요소(A has B)의 메소드만 호출해야한다.

최소지식원칙에도 단점이 있다. 객체들 사이의 의존성을 줄이고, 관리의 용이성을 얻는 반면에 다른 구성요소의 메소드 호출을 처리할 때 래퍼클래스를 만들어야 할 수도 있다. 그러다 보면 래퍼 클래스를 찾고 다시 그 래퍼 클래스가 어떤 클래스를 감싸는지 일일이 확인해야한다. 때문에 시스템이 더 복잡해지거나 개발 시간도 늘어나거나 성능이 떨어질수도 있다.

원칙8

먼저 연락하지 마세요. 저희가 연락 드리겠습니다.(헐리우드 원칙)(Hollywood Principle)

여덟번째 원칙은 템플릿 메소드 패턴에서 등장했다. 고수준 구성요소에서 저수준 구성요소로만 연락이 가게끔 하라는 것이다. 저수준 구성요소에서 시스템에 접속할 수는 있지만 고수준 구성요소를 직접 호출할 수 없게 하라고 한다. 저수준 구성요소는 프로그램에 참여만 하고 언제 어떤식으로 쓰이는지는 고수준 구성요소에서만 결정하라는 것이다.

헐리우드 원칙은 의존성 부패(dependency rot)를 방지한다. 의존성 부패란 고수준 구성요소가 저수준 구성요소에 의존하고 다시 그 저수준 구성요소가 다른 고수준 구성요소에 의존하고 다시 그 고수준 구성요소가 다른 구성요소에 의존하는 것처럼 의존성이 복잡하게 얽혀있는 것을 일컫는다.

위 도형에서 Unit은 추상클래스이며 고수준 구성요소이다. 유닛을 만드는 알고리즘을 장악하고 있고, 메소드의 구현이 필요한 상황에서만 서브클래스의 메소드를 호출한다. 반면에 Marine과 Firebat은 저수준 구성요소이다. Unit으로부터 호출 당하기 전에는 고수준 구성요소인 Unit를 직접 호출하지 않는다.

원칙9

클래스를 바꾸는 이유는 한 가지 뿐이어야 한다.(단일 역할 원칙)(SRP: The Single Responsibility Principle)

마지막 원칙은 이터레이터 패턴에서 등장했다. 클래스에서 원래 그 클래스의 역할외에 다른 역할을 처리하도록 하면 두 가지 이유로 클래스가 바뀔 수 있다. 첫번째 역할때문에 클래스를 바꿔야 할 수도 있지만 두번째 역할때문에 클래스를 바꿔야 될수도 있다.

어느 클래스의 역할이 객체 생성이었는데 여기다가 생성된 객체를 관리하는 기능까지 추가하면 나중에 객체 생성 로직이 변경되고, 객체를 관리하는 컬렉션(집합체)가 변경되면 두 가지 이유로 인해 클래스를 변경해야 한다.

클래스를 고치는 것은 최대한 피해야 한다. 5번째 원칙인 OCP에서도 클래스는 변경에 대해서는 닫혀있어야 한다고 했다. 코드를 변경할 만한 이유가 두 가지 이상이 되면 그만큼 추후에 코드를 고칠 가능성이 크다는 것을 의미한다. 또한 해당 클래스를 변경할 때, 디자인에 있어서 두 가지 부분에 동시에 영향이 미치게된다. 이를 방지하려면 하나의 클래스에서는 한 역할만 맡도록 해야한다.

단일역할원칙을 제대로 지키는 방법은 디자인을 열심히 살펴보고 클래스가 바뀌는 부분에 주의하는 수밖에 없다.