이 포스트는 ‘객체지향의 사실과 오해(조영호)’를 읽고 쓴 글입니다.
왜 객체지향인가?
객체지향이란 무엇인가? 객체를 중심으로 프로그램을 설계한다는 것이다. 그렇다면 왜 객체를 중심으로 프로그램이 설계되어야 하는가?
프로그램은 어떤 분야의 사용자들이 겪는 문제를 해결하기 위해서 제작된다. 이들의 문제를 해결하기 위해서 프로그램은 사용자들이 원하는 여러가지 기능을 제공해야 한다.
사용자들이 원하는 것이 변하지 않는다면 좋겠지만, 사용자의 요구사항은 매번 변한다. 사용자의 요구사항이 변하면 프로그램의 설계와 구현도 영향을 받는다. 요구사항을 만족시키기 위해 설계와 구현도 바뀌어야 한다. 실컷 코드를 완성해 뒀는데 사용자의 요구사항이 조금 바뀌었다고 해서 모든 코드를 갈아엎을 수밖에 없는 상황이 올 수도 있다.
사용자의 요구사항이 바뀌는 것을 막을 수는 없기 때문에, 사용자의 어떤 요구사항이 오더라도 코드는 최소한만 수정하면 되도록 미리 설계하는 것이 최선이다. 훌륭한 설계자는 사용자가 만족할 수 있는 훌륭한 기능을 제공하는 동시에, 예측 불가능한 요구사항 변경에 유연하게 대처할 수 있는 안정적인 구조를 제공하는 능력을 갖춰야 한다고 한다.
안정적인 구조를 설계할 수 있는 프로그래밍 패러다임 중 하나가 객체지향이다. 객체지향에서 객체는 자율적이고 협력적이며, 애플리케이션의 기능을 구현하기 위해 협력에 참여하고 자신의 책임을 수행한다.
객체
객체는 무엇인가? 객체는 명확한 경계를 가지고 서로 구별할 수 있는 구체적인 사람이나 사물이다. 프로그램에서의 객체는 상태와 동작을 가지는 복합적인 자료구조라고도 볼 수 있다.
그런데 프로그램이 복잡해지고 사용하는 객체의 수가 많아지면 객체를 관리하기 어렵다. 그래서 객체의 공통 특성만을 뽑아 그룹으로 묶어 단순화해서 다뤄야 할 가짓수를 줄일 수 있다. 이처럼 객체를 분류해 추상화할 수 있다.
추상화는 불필요한 부분을 제거해 사물의 본질을 드러나게 하는 것이다. 이렇게 하면 복잡한 객체를 이해하기 쉬운 수준으로 단순화할 수 있다. 추상화로 묶인 객체들은 같은 타입에 속한다고 말한다.
같은 타입에 속하는 객체는 그 타입이 가지는 행동과 책임만 수행할 수 있으면 된다. 즉, 객체가 가진 데이터나 행동의 구현은 객체마다 달라도 된다. 이를 다형성이라고 한다. 다형성은 동일한 요청에 대해 서로 다른 방식으로 응답할 수 있는 능력이다.
역할, 책임, 협력
객체는 어떤 역할을 가지고 협력에 참여해서 책임을 수행한다. 책임을 수행하기 위해서는 다른 객체의 도움이 필요할 수도 있는데, 이때 객체는 다른 객체에게 도움을 요청한다. 요청은 메시지를 통해 전달된다. 협력은 메시지의 요청과 응답이 연쇄적으로 일어나며 수행된다.
객체지향에서의 객체는,
- 협력적이다.
- 다른 객체의 요청을 받아들이고, 적절한 응답을 한다.
- 모든 것을 혼자 처리하지 않고, 도움이 필요하면 다른 객체에 도움을 요청한다
- 자율적이다.
- 협력에 참여하는 방법을 스스로 결정한다. 즉, 요청을 처리하기 위해 어떻게 행동할지를 스스로 결정한다.
- 상태를 직접 관리하고, 상태를 기반으로 스스로 판단하고 행동한다
협력 안에서 객체가 자신의 책임을 수행하려면 다른 객체에게서 요청을 받아야 한다. 이때 요청을 보내는 객체는 요청에 대한 적절한 응답을 받는 것에만 관심이 있고, 요청을 받을 객체가 누구인지는 신경쓰지 않는다. 즉, 요청을 받는 객체의 자리는 요청을 처리할 수만 있다면 어떤 객체든 대체 가능하다.
예를 들어, 어떤 음식점에 가서 파스타를 시키려고 한다고 해보자. 그러면 나는 점원에게 주문을 해야 한다. 어떤 점원이 내 주문을 받게 될 지는 중요하지 않다. 중요한 것은 내 주문을 점원이 처리해 주어야 한다는 것이다. 어떤 점원이 주문을 받았든 그 주문은 요리사에게 전달될 것이다. 주문이 어떤 요리사에게 전달되었든, 요리사는 파스타를 만들어서 어떤 점원이 파스타를 서빙하도록 요청할 것이다. 마찬가지로 어떤 점원이 파스타를 서빙하든 나는 음식을 받을 수만 있으면 된다.
다시 말해, 협력(파스타 주문)에는 동일한 역할(점원, 요리사)을 수행할 수 있는 어떤 객체(점원 A, B, 요리사 C, D)든 자리를 대신할 수 있다. 이처럼 역할을 이용하면 협력에 참여하는 객체를 추상화하고, 협력의 구조를 추상화하여 다양한 문맥에서 협력을 재사용할 수 있다. 위 사례에서 메뉴만 바뀐다면 가게가 한식집이든 일식집이든 음식을 제공하기 위해 동일한 협력이 이루어질 것이다.
책임
애플리케이션의 기능은 객체 간의 협력으로 구현된다. 객체는 행위를 수행하며 협력에 참여한다. 즉, 객체에서 중요한 것은 객체가 외부에 제공할 행동, 책임이다.
점원은 주문을 받을 책임이 있고, 그 주문을 요리사에게 전달할 책임이 있고, 완성된 요리를 서빙할 책임이 있다. 요리사는 전달된 주문대로 요리를 할 책임이 있다.
객체는 자신이 맡은 책임을 자율적으로 수행해야 한다. 자율적으로 책임을 수행한다는 것은 스스로 정한 원칙에 따라 판단하고 행동하는 것이다. 객체는 행동에 대한 구체적인 방법이나 절차를 스스로 선택할 수 있다.
객체가 책임을 자율적으로 수행하려면 객체에게 전달되는 요청이 객체의 자율성을 보장해야 한다. 요청은 객체의 자율성을 보장할 수 있을 만큼 포괄적이고 추상적이면서 협력의 의도가 뚜렷이 드러나서 객체가 해야 할 일을 명확히 지시해야 한다. 요청은 객체가 어떻게 행동해야 하느냐가 아니라 객체가 무엇을 해야 하느냐를 설명해야 한다.
다형성
앞에서, 협력에서 동일한 역할을 수행할 수 있는 어떤 객체든 그 자리를 대신할 수 있다고 했다. 요청에 대한 책임을 수행할 수 있다면 어떤 객체든 대체될 수 있다. 이 객체들이 자율적이라면 요청에 대한 행동을 스스로 선택할 수 있다. 하나의 요청에 대해서 서로 다른 유형의 객체들이 다르게 반응할 수 있는 것이다. 이를 다형성이라고 한다.
객체들이 다형성을 만족시킨다는 것은 그 객체들이 동일한 책임을 공유하고 있는 것이다. 요청을 보내는 객체는 자신이 보내는 요청을 수행해 줄 수만 있다면 요청을 받는 객체를 구분할 필요가 없다. 따라서 동일한 책임을 공유하는, 즉 같은 역할을 할 수 있는 객체는 다른 객체로 대체될 수 있다.
- 요청을 보내는 객체는 요청을 받는 객체가 어떤 요청을 받을 수 있다는 것만 알면 된다
- 협력의 세부 수행 방식은 쉽게 수정할 수 있다. 즉, 수신자를 쉽게 교체할 수 있다.
- 협력 수행 방식을 재사용할 수 있다. 객체의 자리는 다른 객체가 쉽게 대신할 수 있다.
따라서 객체에 요청을 보낼 때는 ‘묻지 말고 시켜라’. 객체지향 애플리케이션은 객체들의 자율 공동체이다. 다른 객체의 결정에 간섭해선 안된다. 이렇게 하면 결합도가 낮아지고, 의도가 명확해진다.
인터페이스
인터페이스는 객체가 책임을 수행하기 위해 외부로부터 메시지를 받기 위한 통로로, 객체가 수신할 수 있는 메시지의 목록이다. 인터페이스는 객체의 요소 일부를 외부로 드러내는 것이므로, 외부에 영향을 미치게 된다. 객체의 내부와 외부는 인터페이스를 기준으로 구분된다.
객체의 내부 구현을 수정하는 것은 외부에 영향을 주지 않는다. 그러나 객체의 외부인 인터페이스가 수정되면 외부에서 영향을 받게 된다. 따라서 외부에서 사용할 필요가 없는 인터페이스는 최대한 노출하지 말고, 협력에 필요한 메시지만 노출해야 한다. 이렇게 해서 객체가 외부에 미치는 영향을 최소화할 수 있다.
요구사항은 항상 변경되고, 이에 따라 소프트웨어도 변경된다. 따라서 변경에 영향을 받는 부분을 최소화해야 한다. 변경을 관리하기 위해서 인터페이스와 구현을 분리해서 관리해야 한다. 그렇게 하면 객체의 내부는 변경의 안전지대가 되어 변경에서 자유로워진다.
좋은 협력은 이해하기 쉽고 변경에 유연한 협력이다. 이를 위해서는 협력에 참여하는 객체의 책임이 자율적이어야 한다. 책임에 자율적이라는 것은 객체의 내부와 외부가 엄격히 분리되는 것이다. 내부와 외부가 엄격히 분리되면 객체 내부를 변경해도 외부에서는 영향을 받지 않는다. 따라서 객체 내부를 변경하는 것에서 자유롭다. 객체 외부에서는 객체의 공용 인터페이스만을 이해하면 객체와 상호작용할 수 있다. 따라서 이해하기 쉽다.
정리
객체지향은 안정적인 객체 구조를 바탕으로, 기능을 객체 간의 책임으로 분할한다. 객체는 자율적이고 협력적인 태도로 협력에 참여해 책임을 수행한다. 이때, 객체의 책임의 자율성이 협력의 설계 품질을 결정한다.
- 자율적인 책임은 협력을 단순하게 만든다.
- 자율적인 책임은 의도를 명확하게 표현하여 협력을 단순하고 이해하기 쉽게 만든다.
- 자율적인 책임은 방법이 아니라 의도를 드러내는 하나의 문장으로 표현된다.
- 즉, 책임이 적절하게 추상화된다.
- 자율적인 책임은 객체의 외부와 내부를 명확하게 분리한다.
- 책임을 수행하는 객체는 그 책임을 수행하기 위한 방법을 자율적으로 선택할 수 있다.
- 객체 외부에서는 그 객체가 어떻게 책임을 수행하는지 알 수 없다.
- 객체의 세부 사항은 객체 내부로 캡슐화되고, 인터페이스와 구현이 분리된다.
- 책임이 자율적이면 책임을 수행하는 내부 방법을 변경해도 외부에 영향을 미치지 않는다.
- 변경에 의해 수정되어야 하는 범위가 객체 내부로 한정되고, 따라서 객체 간의 결합도가 낮아진다.
- 자율적인 책임은 협력의 대상을 다양하게 선택할 수 있는 유연성을 제공한다.
- 협력이 더 유연해지고 다양한 문맥에서 재활용될 수 있다.
- 설계가 유연해지고 재사용성이 높아진다.
- 객체가 수행하는 책임이 자율적일수록 객체의 역할을 이해하기 쉽다.
- 객체가 수행하는 책임이 모여 그 객체가 협력에서 수행하는 역할을 전달한다.
- 객체의 응집도를 높은 상태로 유지하기 쉬워진다.