본문 바로가기

IT/오브젝트

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

안녕하세요 남갯입니다.

 

이론보단 실무

이론은 1970년부터 나왔지만 이론보다는 실무가 중요하다고 합니다.

모든 이론의 기반은 실무에서 나온것이고 실무로부터 나온것들을 이론화 한것들이 대부분이기 때문이다.

따라서 실무에서 다양한 규모로 유지보수 하고있지만 효과적인 이론이 발표된것은 거의 없다.

 

이벤트 시작

소극장 홍보 이벤트를 기획하기로 했다.

 

당첨자와 일반 관람객을 구분해서 티켓을 판매한 후에 입장시켜야한다.

 

초대일자를 가지고 있는 초대장

class Invitation {private val whens : LocalDateTime }

티켓을 소지할 수 있는 티켓클래스

class Ticket(val fee : Long = 0L)

 

소지품을 보관하 가방 클래스

class Bag(
    val amount: Long,
    val invitation: Invitation?,
    val ticket: Ticket?
) {
    fun hasInvitation(): Boolean = invitation != null
}

amount 현금

 

자바에서는 Bag 클래스에 생성자를 통해 invitation 생성자를 강제하자

 

코틀린에서는

 

class Bag(
val amount: Long,
val invitation: Invitation? = null,
val ticket: Ticket?
)

이것과 같이 가능하다.

 

관람객 클래스도 만들어보자

 

class Audience(val bag : Bag)

class Audience(val bag : Bag)

 

티켓을 판매하는 판매소도 만들어보자

class TicketOffice(
    var amount:Long,
    val tickets : MutableList<Ticket> = arrayListOf()
){
    fun getTicket() = tickets.removeAt(0)
    fun minusAmount() = amount--
    fun plusAmount() = amount++
}

 

티켓 판매원은 매표소를 알고 있어야한다.

 

class TicketSeller(val ticketOffice: TicketOffice)

이렇게 구현하게 된다면

 

 

 

실제 소극장을 구현한다면 이런형태로 구현이 가능하다.

 

class Theater(private val ticketSeller: TicketSeller){
    fun enter(audience: Audience){
        val ticket = ticketSeller.ticketOffice.tickets
        val audience.bag.ticket = ticket[0]
    } else{
        val ticket = ticketSeller.ticketOffice.getTicket()
            ...
    }
}

 

하지만 이 프로그램은 문제가 있다고 한다.

 

마틴의 말에 따라 모듈은 제대로 실행되야하고 변경에 용이하며 이해하기 쉬워야한다.

제대로 동작하긴 하지만 변경의 용이성과 읽는사람과의 의사소통이라는 목적은 만족하지 못한다.

 

여기서의 문제는 관람객과 판매원이 소극장의 통제를 받는 수동적인 존재라는 것이다.

관람객의 가방을 소극장이 관람객의 가방을 열어본다고 한다. 소극장이 허락도 없이 매표소에 보관중인 티켓과 현금을 마음대로 접근 할 수 있기 때문이다.

 

변경1

 

또한 코드를 이해하기 어려운 이유중에는

코드를 이해하기 위해 여러 가지 세부적인 내용들을 한꺼번에 기억하고 있어야 한다는 점이다. 앞으로 돌아가 Theater의 enter 메서드를 다시 살펴보면 Theater의 enter를 이해하기위해 Audience가 Bag를 갖고있고 Bag안에는 현금과 티켓

TicketSeller가 TicketOffice에서 티켓을 판매하고 등등 여러가지를 기억해야한다.

 

 

 

의존성이 있어서 Audience와 TicketSeller를 변경할 경우 Theater도 함께 변경해야한다. (의존적이다)

즉 이런 의존때문에 변경에 취약하다.

 

변경

 

Theater가 원하는것은 관람객이 소극장에 입장하는 것 뿐이다. 따라서 관람객이 스스로 가방 안의 현금과 초대장을 처리하고 판매원이 스스로 매표소의 티켓과 판매요금을 다루게 한다면 해결되는것(자율적인 존재로 변경)

 

 

1. Theater의 enter 메서드에서 TicketOffice에 접근하는 모든 코드를 TicketSeller의 내부로 숨겨서 실제로 Theater가 TicketOffice를 모르도록 접근하는 코드를 TicketSeller에게 숨기는 것이다.

 

 

TicketSeller에 sellTo 코드를 넣어 직접 접근 하는 코드를 바꾼다.

여기서 TicketSeller에서 getTicketOffice 메서드가 제거됐다는 사실에 주목하면서 객체의 내부적인 사항을 감춰서 캡슐화를 이뤄냈다.

 

Teather는 TicketSeller의 SellTo를 호출하는 코드를 생성.

 

 

 

Audience가 Bag 내부를 직접 접근하니 바꿔보자

 

TicketSeller 다음으로 Audience의 캡슐화를 개선시켜 getBag 메서드를 호출해서 내부 인스턴스의 변경을 해야한다.

아까 변경한 sellTo 메서드에서 getBag()를 직접적으로 접근하는 코드를

 

getBag() 코드를 지우고

 

public buy(Ticket ticket) {

   이코드에 넣으면 된다.

   if(bag.hasInvitation()){

      bag.setTicket(ticket);

      return 0L;

    }else {

      bag.setTicket(ticket);

       bag.minusAmount(ticket.getFee());

       return ticket.getFee();

    }

}

 

 

 

 

이렇게 변경되었다.

 

실제 내부구현을 외부에 노출하지 않고 자신의 문제를 스스로 책임지고 해결한다는 것이다.

 

 

여기서 말하는 서로간의 의존이 높은클래스를 변경하여 결합도를 낮추고

응집도 즉 연관된 작업만 수행하는것을 한곳에 모아야한다.

 

응집도를 높이기 위해서는 객체 스스로가 자신의 데이터를 책임져야한다.

객체는 자신의 데이터를 스스로 처리하는 자율적인 존재여야 하는것이다.

 

절차지향과 객체지향

 

절차지향의 관점에서 Theater에서 enter는 프로세스이고

나머지 클래스들 (Audience.. ) 등은 데이터를 말한다. 프로세스와 데이터를 별도의 모듈에 위치시키는 방식

절차 지향 프로그래밍이라고 부른다.

 

 

이전에 작성한 코드는 절차지향적 코드로

우리의 예상보다 쉽게 벗어나기 때문에 읽는 사람과 원할하게 의사소통하기가 어렵다.

의존도가 높아 버그는 버그를 부르게 된다.

 

이와 반대로 프로세스가 동일한 모듈 내부에  위치하도록 하는 방식을 객체지향 프로그래밍이라 부른다.

 

 

책임의이동

결국 두 방식의 근본적인 차이는 책임의 이동인데,  위에서 처음 표현한 코드는 Theater에 책임이 집중되어 있었다. 수정후에는 각 객체의 책임이 적절하게 분배되고, 따라서 각 객체들은 스스로 책임을 지게된다.