개발공부/객체지향의 사실과 오해

[객(체지향의)사(실과)오(해)] 1장. 협력하는 객체들의 공동체

장아장 2023. 4. 15. 18:09

소스 코드 : https://github.com/JangAJang/the-orient-of-Object-Orientation/tree/feat/Chapter2

 

객체지향에서는 모든 객체를 위주로 일이 돌아간다는 이야기는 자주 들었다. 

그러면 그 일이 어떻게 돌아가는지, 그리고 내가 만든 코드에서 어떻게 이루어져야 조금 더 객체지향적인 결과가 이루어질지 생각해보았다. 

 

일단, 책에 적힌 이론들을 이해해보자. 

 

객체지향 프로그래밍에서는 객체가 스스로 생각하고 결정하게하는, '캡슐화'가 이루어진다. 이를 통해 객체에 자율성이 부여된다. 

이렇게 자율성이 있는 객체들이 협력하는 관계를 가지고, 이 관계 안에서 메시지로 소통을 한다. 

 

객체 ---(협력이라는 관계)--- 객체

이 관계에서 각 객체는 메시지를 통해 소통한다. 

 

책에서는 이에 대한 예시로 커피 전문점을 설명했다. 

 

손님, 캐시어, 바리스타라는 객체를 서로 협력이라는 관계로 연결되어있다고 보았을 때, 하나씩 내용을 빌드업 해보겠다. 

이렇게 협력이라는 관계 안에서 메시지를 주고 받는다. 이를 요청과 응답이라고 한다.

객체는 협력이라는 관계 내에서 메시지를 요청과 응답으로 주고받는다. 

 

그렇게 각 객체는 협력이라는 관게에서 역할과 책임이 존재하게 된다. 

이를 한번 정리해보면, 

  • 손님
    • 역할 :
      • 주문을 요청한다. 
      • 요청한 주문상품을 받는다. 
    • 책임 :
      • 주문을 해야할 책임이 있다.
  • 캐시어
    • 역할
      • 주문을 받는다.
      • 바리스타에게 커피 제작을 요청한다.
      • 바리스타에게 요청한 커피를 받는다.
      • 손님에게 주문상품을 준다.
    • 책임
      • 주문을 받아야 한다.
      • 바리스타에게 커피를 요청해야 한다.  
  • 바리스타
    • 역할
      • 캐시어에게 만들어야 할 커피를 요청받는다. 
      • 커피를 만들어 캐시어에게 준다. 
    • 책임
      • 커피를 만들어야 한다. 

모든 객체는 역할을 받고, 이 역할에 대한 책임을 져야 한다. 

마치, 민주주의 사회에서 말하는 권리와 의무가 있다. 

대한민국 국민인 우리는 자유민주주의 국가의 국민으로써 국가에게 받는 권리, 또는 자유로울 권리등을 받는다. 

그런 만큼 우리는 군대를 가는 것 처럼 의무를 가지게 된다. 

 

객체도 이러한 것 처럼, 각자 역할을 가질 수 있다. 그런 역할을 가진 만큼, 그 역할을 제대로 수행해야할 책임이 있다. 

 

역할은 여러가지 특징을 가지고 있다. 사실 이 부분은 예시가 명확하게 있진 않아서 머리속으로 상상을 하면서 읽어보았다. 

  • 여러 객체가 동일한 역할을 수행할 수 있다
    • 카페에 손님이 한명일 필요는 없다(오히려 그러다 카페 망한다...)
  • 역할은 대체 가능성을 의미한다
    •  캐시어가 하는 주문을 받고, 바리스타에게 커피를 만들어달라 하고, 커피를 받아 손님에게 주는 역할을 꼭 캐시어가 해야할까? 
      맥도날드 가보면, 키오스크가 다 한다. 보통 직원들은 손님들의 문의사항을 받거나, 주문 상품을 만드는 역할만 한다. 
  • 각 객체는 책임을 수행하는 방법을 자율적으로 선택할 수 있다
    • 이 부분이 조금 이해가 되지 않았다. 그러다가 캐시어의 예시를 생각해보았다. 
      음식점의 경우에는, 캐시어가 주문 빌지를 뽑아 주방에 준다. 이를 통해 조리사(카페로 치면 바리스타)가 상품을 만들어 준다. 
      스타벅스의 경우, 캐시어는 잔에다가 어떤 커피이며, 샷은 몇개이고, 휘핑크림이 어떻게 되며, 우유는 넣을지, 얼음은 얼마나 들어갈지, 시럽은 몇 펌프인지 적어서 빈 잔을 바리스타에게 전해준다. 
      음식점과 스타벅스 모두, 주문을 받아 만드는 사람에게 전달해주는 각자 역할에 대한 책임을 수행했다. 그 방법은 다 다른 것이다. 
      정도로 생각해보았다. 
  • 하나의 객체가 동시에 여러 역할을 수행할 수 있다
    • 생각해보면, 카페에서 짬좀 차면, 혼자 주문 받고 혼자 커피만들고 다 할 수 있긴 하다. 실제로 짬 찬 상병쯤 되면 다들 후임들이랑 일 나눴다가, 혼자서 다 커버하는 경우도 있지 않은가?

 

이걸 실제로 우리가 만드는 백엔드 프로젝트에서 생각해보았다. 

로그인을 위한 AuthService가 있다. 

사용자, AuthService, Member라는 객체가 존재한다고 생각해보았다. 

이 때 동작하는 과정을 생각해보자. 

  1. 사용자는 아이디와 비밀번호로 로그인을 요청한다. 
  2. AuthService는 사용자의 요청을 받아 MemberRepository(DB)에서 같은 아이디를 가진 회원을 찾아온다. 
  3. AuthService는 그 회원에게 이 비밀번호가 맞는 비밀번호가 맞는지 물어본다(이 도끼가 네 것이 맞느냐?)
  4. Member는 AuthService에게 받은 비밀번호 메시지의 요청에 대해 맞는지, 틀린지 응답을 한다. 
  5. AuthService는 Member의 응답에 따라 맞으면 토큰이라는 응답을, 아니면 예외라는 응답을 사용자에게 준다. 

이렇게 각 객체간의 협력으로 만들어지는 하나의 기능을 생각해보았다. 그러면 객체간의 역할과 책임은 무엇일까?

  • 사용자
    • 역할 
      • 아이디와 비밀번호라는 메시지로 로그인을 요청한다. 
      • AuthService에게 응답을 받는다. 
    • 책임
      • 아이디와 비밀번호로 요청을 해야한다. 
  • AuthService
    • 역할
      • 사용자에게 아이디, 비밀번호의 요청을 받는다.
      • 해당 아이디를 가지고 있는 Member객체에게 맞는 비밀번호인지 요청을 보낸다. 
      • Member에게 맞는 비밀번호인지 응답을 받는다. 
      • Member의 응답에 따라 회원에게 토큰을, 혹은 예외를 응답해준다.
    • 책임
      • 사용자에게 요청을 받아야 한다. 
      • 요청에 맞는 Member객체를 찾아 그 객체에게 비밀번호가 맞는지 확인을 받아야 한다. 
  • Member
    • 역할 
      • AuthService에게 받은 비밀번호가 맞는 비밀번호인지 확인한다. 
      • 맞는지 틀린지 응답을 준다. 
    • 책임 
      • 비밀번호가 일치하는지 확인해야 한다. 

이런식으로 각 객체의 역할과 책임을 생각해보았다. 

 

혼자서 생각을 해보다가, 혼자서는 무리가 있어서 다른 사람들을 모아서 스터디를 하자고 꼬드겼다

( 책 스터디로 단체 이름은 퉁치겠다. 객사오만 하기에는 클린코드, 좋은코드 나쁜코드, 오브젝트 등도 할게 많으니까.. 그리고 CS도 다른 곳에서 스터디를 찾을까 고민중이다)

 

일단, 위의 카페의 경우와, 아래의 내가 만든 예시를 결국 스터디에 가서 같이 코드 리뷰를 해보았다. 

가장 큰 쟁점은

객체간의 메시지를 어떻게 전달할 것인가?

이 부분에서 다른 사람들은 각자 객체를 클래스로 만들어, 이를 한 객체 안에 담는 경우도 있었지만, 나는 다른 방법을 골랐다. 

클래스 내의 메서드를 만들어, 이에 대한 반환타입을 다른 객체에 담는 방식으로 선택했다. 

 

위에 이야기한 다른 사람들의 방식에서는 객체간의 협응은 활발해지겠지만, 독립성을 헤친다고 느꼈기 때문이다. 

그런데, 내가 사용한 부분도 조금 아쉬운 점이 있었다. 

클래스 메서드 파라미터로만 다른 객체를 담아서 메서드를 처리하면 어떨까? 라는 생각이다. 

예시를 들어보자면, 

 

  • 바리스타 객체
    • String 커피만들기(String 주문){
          return 주문대로 만든 커피(String)
      }
  • 캐셔 객체
    • String 주문 주기(String 고객의 말){
          return 말대로 받은 주문
      }

이런 식으로 처리를 했었다. 이를 조금 더 편하게

  • 바리스타 객체
    • String 커피만들기(캐셔 객체){
          return 커피 만들기(캐셔.주문서 전달하기)
      }
  • 캐셔 객체
    • String 주문 주기(고객 객체){
          return 주문서 만들기(고객 객체. 주문 말하기)
      }

이런 식으로 했을 때 차이점이 무엇이 있을까를 생각해보았다. 

일단, 일반 메시지만을 파라미터로 받을 때

  • 장점
    • 파라미터로 받는 객체를 유지보수하면서 구조가 변경해도 이에 대한 대응이 유연하다. 
    • 객체간에 더욱 독립적이게 존재할 수 있다.
    • 다른 객체를 받아서 동작하는 것이 아닌, 문자열만을 받아 일을 처리한다. 즉, 다른 객체가 일을 대신할 수 있다는 장점이 있다. 
  • 단점
    • 객체간의 연결이 느슨한 느낌이 있다. 스터디를 하면서(아직 2장을 읽지 않은 상태였다) 과연 이게, 객체간에 서로 연결된 상태라고 할 수 있을까 라는 이야기가 나왔었다. 다른 방법으로, 어떻게 객체간에 연결이 더욱 좋아질지 생각해봐야 하는 부분 같다. 
    • 일이 많다. 사실 다른 객체를 파라미터로 받으면, 그냥 그 객체에서 해야 할 일을 만들어두고, 이 객체에서 어떻게 보면 도메인 로직으로 하나의 동작이 완료될 수 있지만, 내가 만들었던 코드는 문자열만 받게 만들었다. 그렇기에, 이를 연결시켜주는 다른 클래스가 필요하다. 

이런 장, 단점이 나왔었다. 

내 개인적인 생각으로는, 카페라는 다른 클래스가 존재해야 하지 않을까 라는 생각이 들었다. 

개발도 개발이지만, 조금 더 근본적으로 다가갔을 때, 과연 카페라는 공간이 없으면, 캐셔/바리스타/손님이 서로 연결되어 일을 수행할까?

카페라는 곳이 아닌, 다른 곳에서 만난다면 그들은 각자의 역할을 수행할 수 있을까? 라는 생각이 든다. 

각자의 역할은 각자 만들고, 그 역할은 카페 안에서 수행되어야 한다고 생각한다. 

즉, 객체가 역할과 책임을 지기 위해서는, 우리는 카페로 들어가야 하지 않을까?

이번 프로젝트에선, 그 '카페'를 어떻게 해야할까를 생각하게 해주었다. 

 

다음에 또 읽어봐야겠다. 

그럼... twenty thousand...!