GYUD-TECH

[스터디: 오브젝트] 좋은 설계 본문

스터디

[스터디: 오브젝트] 좋은 설계

GYUD 2024. 2. 5. 16:16

 

'객체지향의 사실과 오해' 를 읽으면서 객체지향의 개념과 장점, 설계방식에 대해 고민할 수 있었다.

하지만, 책을 읽으면서 계속해서 '그래서 어떻게 객체지향적 설계를 할 수 있는데?' 라는 의문이 들었다.

이를 위해 스스로 예제를 만들어가며 적용해 보았지만, 저자가 생각하는 객체지향적 설계방법이 궁금하였다.

 

이러한 이유로 다음 스터디 서적을 정할 때 자연스럽게 '오브젝트' 가 먼저 떠올랐다.

책의 앞부분에 코드 없이 프로그래밍의 패러다임을 설명하는 것은 영감을 주기 어렵다는 저자의 말에 적극적으로 공감하였고, 어떻게 책을 읽어나갈지 방향을 정할 수 있었다.

 

책에 나와있는 저자의 코드와 내 생각을 비교해보고, '그래서 어떻게 객체지향적 설계를 할 수 있는데?' 라는 질문에 대한 답을 찾는 것을 목표로 책을 읽어나갈 것이다.

 

책을 다 읽은 후에는 모호했던 객체지향의 개념과 적용법을 아는, 좋은 객체지향 개발자가 되고 싶다.

 


객체, 설계

좋은 설계

로버트 마틴은 소프트웨어 모듈이 가져야하는 세가지 기능을 정의한다.

1. 올바르게 실행되어야 한다.
2. 변경에 용이해야 한다.
3. 이해하기 쉬워야 한다.

 

객체지향적 설계도 위의 조건을 달성하기 위한 방법이며, 이는 좋은 설계의 정의와도 부합한다.

 


절차지향적 설계 vs 객체지향적 설계

책에서는 티켓 판매 애플리케이션을 코드로 구현하는 것을 절차지향적 설계를 객체지향적 설계로 변화시키는 과정을 보여준다.

여기서 절차지향적 설계와 객체지향적 설계의 개념을 이해하기 위해서는 먼저 데이터와 프로세스의 개념을 이해해야 한다.

데이터: 특정 기능을 수행하기 위해 필요한 정보를 제공해 주는 곳
프로세스: 특정 기능을 수행하기 위해 처리하는 곳

 

절차지향적 설계에서는 데이터와 프로세스가 별도의 모듈에 위치한다.

다시 말해서, 데이터 외부에서 데이터가 다루어야 하는 기능을 사용하는 것이다.

 

반대로, 객체지향적 설계에서는 데이터와 프로세스가 동일한 모듈에 위치한다.

이를 통해, 객체는 스스로를 통제할 수 있는 자율적인 객체가 될 수 있다.

 

 

책에서 소개한 절차지향적 설계 예제는 아래와 같이 Theater 객체가 모든 객체에 의존하는 형태였다.

 

 

이러한 설계는 제대로 로버트 마틴이 정의한 소프트웨어 모듈의 조건 중 1번 '올바르게 작동해야한다'를 만족시킨다.

 

하지만, Audience가 가방이 아닌 지갑을 들고 있다면 Audience 뿐만 아니라, Theater의 메서드도 변경되어야 하기 떄문에 2번 '변경에 용이해야한다' 라는 조건은 만족시키지 못한다.

 

또한, Theater라는 객체가 Audience가 Bag를 들고 있는지 알고, 이 가방에 Invitation이 있는지 접근하는 것은 현실세계의 티켓 구매 프로세스와는 매우 다르기 때문에 3번 '이해하기 쉬워야 한다' 를 만족시키지 못한다.

 

 

이를 해결하기 위해 getter() 메서드로 외부에서 객체를 가져와서 프로세스를 수행하는 대신, 인터페이스 메서드를 통해서 객체에 접근하게 함으로써 객체의 책임을 내부로 이동시키는 방식으로 변경하였다.

 

이렇게 설계하더라도, 프로세스는 정상적으로 작동하기 때문에 1번 조건을 만족시킨다.

 

Audience가 가방이 아닌 지갑을 가지고 있는 경우에도 Audience 객체만 변경해 주면 되기 때문에 2번 조건을 만족시킨다.

 

Theater 객체는 오로지 ticketSeller 만 알고, Audience의 Bag에 직접 접근하거나, ticketOffice의 티켓에 직접 접근하지 않기 때문에 우리가 생각하는 현실의 도메인과 비슷하여 3번 조건을 만족시킨다.

 


무조건 다 내부로 옮기는 것이 좋을까?

하지만 이렇게 객체의 책임을 내부로 옮기는 과정에서 오히려 의존관계가 더 많아지는 경우가 발생했다.

public class TicketSeller {
    
    private TicketOffice ticketOffice;
    
    public void sellTo(Audience audience) {
    	ticketOffice.plusAmount(audience.buy(ticketOffice.getTicket());
    }
}


public class TicketOffice {
    
    private Long amount;

    public void plusAmount(Long amount) {
        this.amount += amount;
    }
}

위  코드는 TicketSeller 객체가 Audience에게 Ticket을 팔고 TicketOffice에 돈을 추가하는 프로세스 코드이다.

 

TicketSeller의 책임은 'Audience가 TicketOffice에서 티켓을 구매하도록 한다' 로 정의할 수 있다.

하지만 위의 코드는 직접 TicketOffice의 amount를 증가시키는 메서드를 호출하여 TicketOffice의 책임까지 수행하고 있다.

 

따라서 아래와 같이 코드를 변경할 수 있다.

public class TicketSeller {
    public void sellTo(Audience audience) {
        ticketOffice.sellTicketTo(audience);
    }
}


public class TicketOffice {
    public void sellTicketTo(Audience audience) {
        plusAmount(audience.buy(getTicket()));
    }
    
    private void plusAmount(Long amount) {
        this.amount += amount;
    }
}

이렇게 설계를 변경하면, TicketSeller는 티켓을 팔았을 때 TicketOffice의 amount가 증가되는지는 알 수 없고, Audience가 TicketOffice에서 티켓을 구매하도록 도와주는 책임만 수행한다.

 

하지만, 이러한 변경을 통해 TicketOffice가 Audience를 의존하게 되어 오히려 의존관계가 늘어나는 문제가 발생했다.

 

변경을 통해서 TicketSeller가 Audience 객체의 내부를 모르기 때문에 TIcketSeller의 자율성은 증가하였다.

하지만, TicketOffice는 Audience 객체를 추가로 참조해야되서 TicketOffice의 자율성은 감소하였다.

 

이런 경우에는 어떠한 설계가 좋은 설계일까?

고민 될때는 앞서 보았던 소프트웨어 모듈의 세가지 기능을 다시 떠올려 보자.

1. 올바르게 실행되어야 한다.
2. 변경에 용이해야 한다.
3. 이해하기 쉬워야 한다.

 

어떤 객체를 자율적으로 만드는 지는 trade off 관계이기 때문에, 변경될 가능성이 높은 객체를 자율적으로 만드는 것이 더 좋은 코드라고 생각한다.

 

결국 좋은 객체지향의 설계에 정답은 없지만 시스템의 특징을 고려한다면 효율적인 설계 방법이 존재한다.

 

좋은 객체지향 코드란 시스템의 특성을 고려하여
기능이 잘 동작하고,
변경에 용이하며,
이해하기 쉬운 코드이다.