글 작성자: 자바니또

개요

지금까지 자연스럽게 사용한 단어들이 있다. 책임, 위임 그리고 협력이 오늘 배울 주제이다.
객체지향적으로 생각하고 설계를 하기 위해서는 꼭 배워야할 중요한 개념이다.

책임

객체지향 설계원칙중에는 단일책임원칙(SRP)이 있다.
모든 클래스는 하나의 책임만 가져야한다. 또는 클래스를 변경하려는 이유는 하나여야 한다고도 한다.

필자가 학교에서 배울 때 이 책임이라는 말이 와닿지가 않았었다. 책임이 무엇이길래 하나만 가져야하고 클래스를 변경하려는 이유라는 말은 또 무엇인가?

책임은 클래스가 책임져야 할 능력이다. 카페의 바리스타를 예로 들어보자. 바리스타가 가져야 할 능력은 우선 커피를 만들 수 있어야 한다는 것이다.
또 커피에 대한 설명을 할 수 있어야 하고, 크기가 작은 카페라 서빙까지 해야한다고 해보자.

public class Barista{
    public void makeCoffee(Order order){
        // 커피를 만들고 반환하는 로직
    }
    public void explainAbout(Coffee coffee){
        // 커피에 대한 정보를 설명하는 로직
    }
    public void serving(Coffee coffee, int orderNum){
        // 주문번호에 맞는 손님에게 커피를 서빙하는 로직
    }
}

이 정도가 적당 할 것 같다. 현재 바리스타가 가진 책임은 몇가지 일까?
커피를 만들 수 있는 능력, 설명할 수 있는 능력, 서빙할 수 있는 능력 이렇게 3가지일까?
그럴 수도 있고 아닐 수도있다. 책임의 가짓수는 public method의 개수와는 다르다.

카페의 규모를 유지하고 바리스타의 1인 체제로 변치 않을 것이라면 복잡하게 책임을 나눌 필요 없이 뭉뚱그려 '운영'이라는 책임을 바리스타에게 줄 수도 있을 것이다.
책임의 기준은 개발자의 의도된 설계에 따라 다를 수 있다. 중요한 것은 수정상황에 대비하여 적당한 크기로 책임을 정해 클래스에 부여 하는 것이다.

너무 멀리까지 예상하여 책임을 모두 분할 해버리는 것은 좋지 않다.
현재 설계에서 발생가능성이 높은 수정상황에 대한 대비하여 감당할 수 있는 크기의 책임을 정하는 것 만으로 우선은 충분 할 것이다.

카페를 조금 확장할 생각이 있다면 당연히 바리스타 혼자 커피도 만들고 서빙도 하는 것은 힘들다.
바리스타는 커피만 만들기도 바쁠 것이다. 서빙과 커피에 대한 설명은 종업원에게 맡겨야 한다.

public class Barista{
    public void makeCoffee(Order order){
        // 커피를 만들고 반환하는 로직
    }
}
public class Employee{
    public void explainAbout(Coffee coffee){
        // 커피에 대한 정보를 설명하는 로직
    }
    public void serving(Coffee coffee, int orderNum){
        // 주문번호에 맞는 손님에게 커피를 서빙하는 로직
    }
    //...
}

종업원에게 서빙과 설명을 맡곁다. 이제 바리스타의 클래스가 수정되어야 한다면 어떤 이유일까? 커피를 만드는 것에 대한 이유일 것이다.
수정의 이유 또는 변경하려는 이유는 커피를 만드는 것에 변화 뿐일 것이다.
메서드를 몇가지 더 추가한다해도 커피를 만드는 것에 대한 메서드라면 변경의 이유는 추가되지 않는다.
따라서 Barista는 하나의 책임을 가진다고 할 수 있다.

하나의 책임을 지면서 SRP원칙을 지킬 수 있게 되었다. 무엇이 좋아졌을까? 이전의 코드를 생각해보자.
필요한 종업원의 종류가 10종류인데 그 책임을 바리스타 혼자 하게 되었다면, 클래스 안의 코드가 너무 길어지게 되고 수정사항이 발생했을 때 수정해야 할 코드를 찾기 힘들 것이다.
하지만 책임을 나눔으로써 여러 종업원의 책임은 각 종업원이 지도록 할 수 있고 수정사항이 발생 했을 때 해당 클래스만 찾으면 쉽게 수정 할 수 있다.

위임

Employee를 보자 현재 설계에 이 정도가 적당하다고 한다면 개발자는 어떤 책임을 부여한 것일까?
갖다 주는 커피에 대한 책임을 종업원에게 준 것이다.

그런데 카페의 운영방침이 손님으로부터 커피설명요청을 받으면 어플을 통해 미리 녹음되어 있는 음성을 켜도록 바뀌었다고 가정해보자.
그렇다면 실질적으로 종업원은 설명 요청을 받기만 하고 설명에 대한 책임은 어플이 지게되었다.

public class Employee{
    public void explainAbout(Coffee coffee){
        // ...
        cafeApp.explainAbout(coffee);
        // ...
    }
    public void serving(Coffee coffee, int orderNum){
        // 주문번호에 맞는 손님에게 커피를 서빙하는 로직
    }
    // ...
}
public class CafeApp{
    public void explainAbout(Coffee coffee){
        // 커피에 대한 정보를 설명하는 로직
    }
}

위임은 책임을 대신 져줄 수 있는 객체에게 메시징을 보내는 것을 말한다.
위의 코드를 보자. Employee가 요청을 받으면 메시징을 통해 CafeApp에게 책임을 떠넘 기고 있다.

왜 위임을 할까?

CafeApp이 생기면서 Employee가 커피설명까지 해야하는 건 책임이 너무 무겁다고 생각된다.
그렇다고 손님이 CafeApp을 모를 수도 있는데 Employee가 손님의 요청을 받지 않는 것도 이상하다.
그렇다면 요청을 받고 대신 수행할 수 있는 CafeApp에게 전달 하면된다. 원래라면 Employee가 져야할 책임을 조금 덜어내어 CafeApp에게 넘기는 것이다.

explaintAbout()이 100줄짜리 코드였다고 가정해보자.
그것을 CafeApp에게 넘김으로써 Employee는 서빙에 대한 책임만 갖게되고 CafeApp은 커피설명에 대한 책임만 갖게 되었다.
이렇게 책임이 알기쉽게 나누어져있다면 수정사항이 발생했을 때 우리가 받는 스트레스는 줄어들 것이다.
클래스의 명을 책임이 분명히 드러나도록 짓는다면 더욱 효과가 좋다.

지금까지 새로운 클래스를 생성하고 위임을 통해 책임을 나누었다.
책임을 나눔으로써 커피를 만드는 코드에 관한 것은 Barista클래스, 서빙에 관한 코드는 Employee클래스, 커피 설명에 대한 코드는 CafeApp에 모임으로써 응집도가 높아졌다.
이제 이렇게 책임 별로 나누어진 클래스가 객체가 되어 서로 소통하는 협력에 대해 알아보자.

협력

객체지향 세계에서는 오직 한 가지의 의사소통 수단이 존재하는데, 이것이 이전 포스트의 주제인 메시지이다.
객체는 무엇인가를 수행하기 위해 자신이 가지고있는 정보를 이용하고 자신이 가지고 있지 않은 정보라면 그 정보를 가지고 있는 객체에게 메시징을 하기도 하고 반대로 받기도 하는데, 이것을 서로 협력 한다고 한다.

Employee와 CafeApp의 관계도 커피설명에 대한 책임을 위임함으로써 CafeApp이 Employee에게 협력한다고 할 수 있다.

public class Barista{
    public void makeCoffee(Order order){
        CoffeeName coffeeName = order.getCoffeeName();
        Coffee coffee = get(coffeeName);
    }
    private Coffee get(CoffeeName coffeeName){
        //그라인더로 원두를 갈고 커피를 만드는 로직
    }
}
public class Employee{
    /...
    public void takeOrder(Order order){
        barista.makeCoffee(order);
    }
}
public class Customer{
    public void order(Order order){
        //...
        employee.takeOrder(order);
        /...
    }
}

Barista와 Employee의 코드를 좀 더 추가하여 써보았다. 실제로는 이것보다는 더 복잡한 과정을 거치겠지만 설명을 위해 이정도가 좋을 것 같다.
고객은 종업원에게 주문을 하고 종업원은 바리스타에게 커피를 만들도록 메시징한다.
Barista와 Employee는 고객의 주문에 대한 처리를 위해 서로 협력하고 있다. 이것은 앞에서 말한 위임과 같다.

또 하나 협력하고 있는 클래스가 있는데, 바로 Order클래스 이다.
바리스타는 받은 주문으로부터 커피의 이름을 알아내고 이름에 맞는 커피를 만들어 낸다.

고객이 주문한 커피의 이름을 바리스타가 아는가?

바리스타는 고객이 무슨 커피를 주문 했는지 알지 못하지만 커피를 만들기 위해서는 커피의 이름을 알아야 한다.
커피의 이름은 누가 알고 있는가? Order밖에 없다.
따라서 Barista는 Order에게 getCoffeeName()을 통해 커피이름을 알려달라고 해야한다.
자신이 모르는 정보를 아는 객체에게 대신 정보 처리를 요청하는 것, 그것이 메시징이고 메시징으로 협력은 이루어진다.

결론

  • 책임은 객체 자신이 져야 할 능력에 대한 책임이다.
  • 책임은 같은 코드라도 개발자의 설계 의도에 따라 더 나눌 수도 있고 덜 나눌수도 있다.
  • 위임은 자기 대신 처리 할 수 있는 객체에게 메시징을 함으로써 책임을 넘기는 것이다.
  • 협력은 객체지향 세계에서 객체끼리 서로 메시징을 통해 소통하는 것을 말한다.