6일 전

객체지향 프로그래밍이라고 하면 보통 캡슐화, 상속, 다형성, 추상화 같은 단어부터 떠오른다. 그리고 실제로 자바나 코틀린을 배우다 보면 인터페이스를 만들고, 구현체를 분리하고, 상속 구조를 설계하는 식으로 객체지향을 접하게 된다.
그런데 객체지향을 조금 더 깊게 보면, 핵심은 문법이 아니다. 진짜 중요한 것은 객체에게 어떤 역할을 맡길지, 어떤 책임을 줄지, 그리고 객체들이 어떻게 협력하게 만들지다. 즉, 객체지향의 본질은 “클래스를 잘 나누는 기술”이라기보다, 협력하는 객체들의 구조를 설계하는 사고방식에 가깝다.
객체지향을 공부할 때 가장 먼저 체감되는 부분은 보통 인터페이스를 이용한 추상화다. 예를 들어 슬랙으로 알림을 보내는 기능이 있다고 하자.

이 인터페이스를 구현한 실제 어댑터는 다음처럼 만들 수 있다.

이 구조의 장점은 분명하다. 코드가 “슬랙을 어떻게 호출하는지”에 직접 묶이지 않고, “메시지를 보낸다”는 추상적인 역할에 의존하게 된다.
그런데 여기서 한 가지 문제가 생긴다. 테스트를 돌릴 때마다 실제 슬랙 메시지가 날아간다면 꽤 시끄럽고 불편하다. 이럴 때 다형성을 이용하면 테스트 환경에서는 다른 구현체를 꽂아 넣을 수 있다.

이렇게 하면 운영 환경에서는 SlackAdapter가 동작하고, 테스트 환경에서는 TestSlackAdapter가 동작한다. 즉, 기능의 큰 의미는 유지하면서 구현은 바꿔 끼울 수 있게 되는 것이다.
이런 구조는 객체지향의 중요한 장점 중 하나다. 변경에 유연해지고, 구현체가 바뀌어도 사용하는 쪽의 코드는 크게 바뀌지 않는다. 그래서 많은 개발자들이 객체지향을 “인터페이스와 다형성을 잘 활용하는 것”으로 이해한다.
하지만 객체지향은 여기서 끝나지 않는다.
인터페이스와 다형성은 분명 중요하다. 하지만 그것만으로는 아직 반쪽짜리 객체지향이다.
객체지향에서 더 중요한 질문은 이것이다.
“이 객체는 무슨 일을 해야 하지?” “이 책임을 정말 이 객체가 맡는 게 맞나?” “객체가 스스로 판단하고 행동하고 있나?”
즉, 객체를 단순한 데이터 상자가 아니라, 자기 책임을 가진 존재로 봐야 한다는 것이다.
두 번째 글에서 말하듯이 객체지향의 핵심은 역할(role), 책임(responsibility), 협력(collaboration) 이다. 객체는 혼자 존재하는 것이 아니라, 시스템 안에서 다른 객체와 메시지를 주고받으며 협력한다. 그리고 그 협력 안에서 자기 책임을 수행한다.
객체지향 시스템은 자율적인 객체들의 공동체라고 볼 수 있다. 각 객체는 혼자 모든 일을 하는 것이 아니라, 서로에게 메시지를 보내면서 기능을 완성한다.
예를 들어 “주문을 생성한다”는 기능이 있다고 해보자. 이 기능은 하나의 객체만으로 끝나지 않을 수 있다.

여기서 중요한 건, 주문 객체가 있고 결제 객체가 있고 알림 객체가 있다는 사실 자체가 아니다. 더 중요한 건 이들이 하나의 기능을 완성하기 위해 협력하고 있다는 점이다.
즉, 객체는 그냥 존재하는 것이 아니라 협력 안에서 의미를 가진다. 어떤 객체가 왜 필요한지를 알려면, 먼저 그 객체가 어떤 협력에 참여하는지를 봐야 한다.
그래서 객체지향 설계는 보통 이렇게 흘러가야 한다.
시스템이 해야 하는 기능을 본다. 그 기능을 위해 어떤 협력이 필요한지 본다. 그 협력 안에서 어떤 책임이 필요한지 정한다. 그 책임을 가장 잘 수행할 수 있는 객체에게 맡긴다.
이 흐름이 중요하다. 처음부터 “Member 클래스에는 필드가 뭐가 들어가야 하지?”처럼 상태부터 보는 게 아니라,“이 객체는 어떤 메시지에 응답해야 하지?”를 먼저 생각해야 한다.
예를 들어 “18세 이하만 프로모션에 참여할 수 있다”는 규칙이 있다고 하자. 이걸 처음에는 이렇게 짤 수 있다.

처음 보면 별 문제 없어 보인다. 규칙이 MemberService 안에 있고, Member를 받아서 결과를 반환하니까 그럴듯해 보인다.
그리고 Member는 그냥 이렇게 생겼다고 하자.

이 구조의 문제는 Member가 너무 수동적이라는 점이다. 이 객체는 자기 상태를 가지고 있으면서도, 정작 자기와 관련된 판단은 하나도 하지 않는다. 그저 데이터를 담아두고, 밖에서 꺼내다 쓰기만 한다.
이렇게 되면 어떤 일이 생기냐면, 시간이 갈수록 서비스 계층에 로직이 계속 쌓인다.

결국 MemberService는 점점 뚱뚱해지고, Member는 아무 책임도 없는 데이터 구조가 된다. 이런 구조를 흔히 Fat Service라고 부른다.
같은 문제를 더 객체지향적으로 볼 수도 있다. “프로모션 참여 가능 여부”는 누가 제일 잘 판단할 수 있을까?
정답은 Member다. 왜냐하면 필요한 정보인 나이를 Member가 가장 잘 알고 있기 때문이다. 즉, 이 책임의 정보 전문가는 Member다.
그래서 책임을 이렇게 옮길 수 있다.

이제 코드를 읽으면 훨씬 자연스럽다.

이 코드는 “서비스가 멤버를 검사한다”는 느낌보다 “멤버가 자기 상태를 바탕으로 스스로 판단한다”는 느낌이 강하다.
이 차이가 중요하다. 이제 Member는 더 이상 수동적인 데이터 덩어리가 아니다. 자기 책임을 가진 객체가 된다.
캡슐화라고 하면 흔히 “필드를 private으로 두는 것” 정도로 이해하기 쉽다. 물론 그것도 맞는 말이다. 하지만 진짜 중요한 건 접근제한자 자체가 아니다.
캡슐화의 핵심은 데이터와 그 데이터를 다루는 행동을 한곳에 모으는 것이다.
예를 들어 이런 코드는 캡슐화가 약한 구조다.

왜냐하면 외부가 Member 내부 상태를 직접 꺼내서 판단하고 있기 때문이다. 이 판단 규칙이 여기저기 흩어지기 시작하면, 나중에 정책이 바뀌었을 때 수정할 곳도 많아진다.
반면 이런 코드는 더 캡슐화된 구조다.

외부는 “참여 가능하냐”만 물어본다. 어떻게 판단하는지는 Member가 알아서 한다. 이렇게 되면 외부는 내부 규칙을 몰라도 되고, 내부 구현이 바뀌어도 영향이 적다.
즉, 캡슐화는 단순히 숨기는 기술이 아니라, 객체를 자율적으로 만들고 변경의 파급효과를 줄이는 기술이다.
##7. 메시지가 객체를 결정한다
객체지향 설계에서는 종종 객체가 메시지를 고르는 것이 아니라, 메시지가 객체를 결정한다고 말한다.
이 말은 어렵게 들리지만 뜻은 단순하다.
먼저 “무슨 요청이 필요한가?”를 생각하라는 뜻이다.
예를 들어 우리가 정말 필요한 것은 이것일 수 있다.

이 메시지가 먼저 정해지면, “그 메시지에 응답할 객체는 누구지?”라는 질문이 따라온다. 그러면 자연스럽게 Member가 선택된다.
반대로 객체부터 먼저 정해놓고 “얘한테 뭘 시킬까?”라고 접근하면, 쉽게 상태 중심 설계로 흘러간다.
즉, 좋은 객체지향 설계는 보통 이런 순서로 간다.
// 필요한 메시지

// 그 메시지를 처리할 객체

이렇게 하면 객체는 꼭 필요한 행동만 외부에 드러내게 되고, 불필요하게 많은 메서드를 갖지 않게 된다.
객체지향을 처음 배우는 사람들이 자주 하는 실수는 이렇다.
먼저 필드를 정한다. 그다음 getter/setter를 만든다. 그 후에 서비스를 만들어서 로직을 넣는다.
하지만 객체지향에서는 순서가 반대에 가깝다. 먼저 행동을 생각해야 한다.
예를 들어 “회원은 프로모션에 참여할 수 있어야 한다”는 행동이 먼저 보이면,
그다음에 “그 판단에 필요한 상태가 뭐지?”를 생각하게 된다.
그러면 자연스럽게 age 같은 필드가 따라온다.

즉, 상태는 목적이 아니라 수단이다. 객체는 필드를 보관하기 위해 존재하는 것이 아니라, 협력 안에서 행동하기 위해 존재한다.
역할은 특정 객체 하나를 가리키는 개념이 아니다. 어떤 책임을 수행할 수 있는 자리라고 보면 된다. 그래서 같은 책임을 수행할 수 있는 여러 객체가 하나의 역할을 공유할 수 있다.
아까 본 슬랙 예시를 다시 보면 이해가 쉽다.

이 인터페이스는 단순한 문법 장치가 아니다. “메시지를 보낸다”는 역할을 표현한 것이다.
운영 환경에서는 실제 슬랙으로 보내는 객체가 이 역할을 맡고,

테스트 환경에서는 아무것도 하지 않는 객체가 같은 역할을 맡는다.

둘은 내부 구현은 다르지만, 협력 안에서는 둘 다 “메시지를 보낸다”는 같은 역할을 수행한다.
그래서 역할 중심으로 설계하면 협력이 유연해진다. 구체 객체가 바뀌어도 협력 구조는 그대로 유지될 수 있기 때문이다.
정리하면 객체지향 프로그래밍은 단순히 클래스, 상속, 인터페이스를 사용하는 기술이 아니다. 그보다 더 본질적으로는 다음 질문에 답하는 설계 방식이다.
이 기능을 위해 어떤 협력이 필요한가? 그 협력 안에서 어떤 책임이 필요한가? 그 책임을 가장 잘 수행할 수 있는 객체는 누구인가? 객체가 자기 일을 스스로 판단하고 행동하고 있는가?
좋은 객체지향 설계에서는 객체가 단순한 데이터 묶음으로 남지 않는다. 객체는 자기 상태를 감추고, 필요한 행동만 외부에 드러내며, 다른 객체와 메시지를 주고받으면서 협력한다.
그래서 객체지향의 핵심 질문은 “이 클래스에 필드를 뭐 넣지?”가 아니다.
진짜 중요한 질문은 이것이다.
“이 객체는 어떤 책임을 가져야 하지?” “이 객체는 누구와 협력하지?” “이 객체는 정말 자기 일을 스스로 하고 있나?”
이 질문에 충실할수록 객체는 더 객체다워지고, 시스템은 더 유연하고, 더 변경에 강한 구조가 된다.
댓글 0