[오브젝트] 1장 객체, 설계

woohobi·2021년 12월 26일
0

Book Review

목록 보기
1/5

객체, 설계

소프트웨어 모듈이 가져야 하는 세 가지 기능

  • 제대로 실행되어야 함
  • 변경이 용이
  • 이해하기 쉬워야함
class Theater{
  public void enterV1(Audience audience) {
          if (audience.getBag().hasInvitation()) {
              Ticket ticket = ticketSeller.getTicketOffice().getTicket();
              audience.getBag().setTicket(ticket);
          }else{
              Ticket ticket = ticketSeller.getTicketOffice().getTicket();
              audience.getBag().minusAmount(ticket.getFee());
              ticketSeller.getTicketOffice().plusAmount(ticket.getFee());
              audience.getBag().setTicket(ticket);
          }
      }
}

위의 코드는 실행이 가능하지만, Theater는 TicketSeller와 Audience에 강하게 의존하고 있어 변경이 취약하고 유지보수를 힘들게 만든다. 또, 개발자는 기억력을 자신의 훌륭한 기억력을 뽐내는 직업이 아니다. 협업을 하기 위해서, enter 라는 함수를 실행하기 위해, 다른 클래스의 어떤 함수와 변수를 가지고 있는지 모두 기억해내야 한다.

따라서 강한 의존성을 낮추기 위해서 Audience와 TickerSeller를 자율적인 존재 로 만들어야 한다.

  • 자율적인 존재란?
    객체는 역할을 가지고, 이에 적합한 책임을 수행하기 위해 다른 객체와 메세지를 통해 적절하게 협력하는 존재이다.

따라서, Theater에서 Audience의 Bag에 접근하고, 돈을 꺼내는 행위는 Theater에서 적절한 역할이 아니고, 이는 TicketSeller와 Audience에서 실행되어야 하는 행동이다. 의존성을 낮추기 위해 아래와 같이 코드를 바꾸어주었다.

class Theater{
    public void enterV2(Audience audience) {
        ticketSeller.sellTo(audience);
    }
}

티켓 판매원
class TicketSeller(){
    public void sellToV1(Audience audience) {
        if (audience.getBag().hasInvitation()) {
            Ticket ticket = ticketOffice.getTicket();
            audience.getBag().setTicket(ticket);
        }else{
            Ticket ticket = ticketOffice.getTicket();
            audience.getBag().minusAmount(ticket.getFee());
            ticketOffice.plusAmount(ticket.getFee());
            audience.getBag().setTicket(ticket);
        }
    }	
}

위와 같이 바꾸어줌으로써, Theater이 TicketSeller와 Audience에 과도한 의존성을 해결할 수 있었다. 하지만, 여전히 TicketSeller가 Audience 내부 함수에 의존하고 있는 문제가 남아있다. Audience의 사소한 변경이라도 TicketSeller가 영향을 받기 때문에 변경에 취약하다. 한 번 더 의존성을 줄여보자.

class TickerSeller{
    public void sellToV2(Audience audience) {
        ticketOffice.plusAmount(audience.buy(ticketOffice.getTicket()));
    }
}

class Audience{
    public Long buy(Ticket ticket) {
        if (bag.hasInvitation()) {
            bag.setTicket(ticket);
            return 0L;
        }else{
            bag.setTicket(ticket);
            bag.minusAmount(ticket.getFee());
            return ticket.getFee();
        }
    }
}

위와 같이 바꾸어줌으로써, 객체들끼리 약한 의존관계를 갖고, 응집도 높은 코드를 만들 수 있었다. 객체지향에서 가장 중요한 지점이 책임과 협력이라고 생각하는데, 객체들끼리 높은 의존도는 변경에 취약하게 만든다. 따라서 객체들은 내부의 상태에 대해 캡슐화를 해야하고, 협력은 메세지를 통해 이루어져야 한다. 첫번째 코드에서는 Theate에 의해 프로세스가 제어되고 책임이 한 객체에 몰려있었던 것에 비해 변경된 코드는 모든 객체를 자율적인 책임을 뿌여하였다.

하지만, 객체에 자율을 부여하는 것이 실제 상황에서 항상 유용할까?

위의 코드를 보면 Audience는 여전히 Bag에 의존하고 내부 상태를 알고 있다. 메세지를 통해 협력하게 바꾸어보자.

public class Audience{
    public Long buyV2(Ticket ticket) {
        return bag.hold(ticket);
    }
}

public class Bag{
    public Long hold(Ticket ticket) {
        if (hasInvitation()) {
            setTicket(ticket);
            return 0L;
        }else{
            setTicket(ticket);
            minusAmount(ticket.getFee());
            return ticket.getFee();
        }
    }	
}

이로써 Bag안으로 캡슐화를 해서 의존성을 낮추었다. 하지만 이번 변경은, tradeoff가 존재한다. 기존에는 Bag 객체가 전혀 Ticket에 대해서 몰랐지만, 변경으로 인해 Bag와 Ticket의 결합도가 증가하였기 때문이다. 실제 개발할때는 단순히 하나의 객체에 대해 자율성을 높이기 보다는 전체 설계 관점에서 고민한 후에 진행해야 될 부분이다.

그래서 좋은 설계가 뭔데?

개인적으로 코드는 유지보수 되기 위해 존재한다고 생각한다. 변경되지 않는 코드는 없고, 내가 아닌 누군가가 내 코드를 담당하게 되더라도 변경에 용이해야 한다. 설계 단계는 물론, 실제 서비스를 하다보면 요구사항 변경, 기능 추가 변경으로 필연적으로 코드는 변경될 수 있다는 점을 고려하고 설계를 진행해야 한다.
이를 객체 지향 관점에서는 자율, 책임, 협력 을 통해 적절한 결합도와 의존성을 가지게 해 변경에 용이한 설계를 만들고 있다.

profile
CDD - Coffee Driven Development

0개의 댓글