002. (The Essence of Object-Orientation) 2. 행위와 상태

2. 행위와 상태

2.1. 객체지향과 인지 능력

인간은 본능적으로 세상을 독립적이고 식별 가능한 객체의 집합으로 바라본다.

객체지향 또한 세상을 자율적이고 독립적인 객체들로 분해하는 인간의 기본적인 인지능력을 기반으로 하여 이해하기 쉬운 패러다임인 것이다.

세상을 더 작은 객체로 분해하는 것은 세상 그 자체가 가진 복잡성을 극복하고 단순화하기 위한 노력이며,

객체지향 패러다임 또한 복잡한 시스템 혹은 애플리케이션을 많은 객체들의 협력으로 보고 이를 분해하여 단순화하는 것으로부터 출발한다.

다만 현실 세계와 소프트웨어 세계 사이의 유사성은 여기에 그치고, 실제로 소프트웨어 세계의 객체는 현실 세계의 객체와는 전혀 다른 모습을 보인다.

2.2. 상태를 결정하는 행위, 행위의 결과를 결정하는 상태

현실 세계에 존재하는 속도계를 소프트웨어의 세계로 옮겨보자.

이 속도계는 현재 표시되고 있는 속력과, 속력의 증가 혹은 감소를 행위로 가지고 있다.

간략하게 코드로 표현해보면 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
class Speedometer {
private var speed = 0

fun increase() {
speed += 20
}

fun decrease() {
speed -= 20
}
}

Speedometer 클래스의 객체를 생성하면 현재 속력이 0이라는 상태를 획득하게 된다.

이때 increase() 혹은 decrease()와 같은 행위를 수행하면 현재 상태를 증가시키거나 감소시켜, 상태를 변경하도록 결정한다.

그리고 행위 이후 결과값은 기존의 상태에 따라서 다시 또 결정된다.

이처럼 상태에 따라 행위의 결과는 달라질 수 있으며, 어떤 행위의 성공 여부는 이전에 어떤 행위들이 발생했는지에 따라서 또 달라짐을 알 수 있다.

즉 행위 간의 순서는 중요한 것 중 하나이며, 어떤 행위를 위해 꼭 선행되어야 하는 행위가 존재할 수도 있다.

다시 한 번 위의 Speedometer 클래스를 살펴보자.

현실 세계의 속도계와 어떤 차이가 있을까?

현실 세계에서 속도계의 속력은 음수가 될 수 없다.

반면 소프트웨어 세계에서는 음수로 표현이 된다.

이를 현실 세계와 가깝게 만들면서, 행위 간의 선행 구조를 만들기 위해 어떤 제약조건을 부여해보자.

제약 조건은 속력이 0인 경우에는 속력의 감소에 해당하는 decrease()가 상태를 바꾸지 못하게하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Speedometer {
private var speed = 0

fun increase() {
speed += 20
}

fun decrease() {
if (speed == 0) {
return
}
speed -= 20
}
}

이제 좀 더 현실 세계에 가까운 속도계 객체가 되었다.

실질적인 속력의 감소 행위를 위해서는 속력을 증가시키는 increase()가 선행되어야할 것이다.

참고 개념적인 설명을 위한 예시로 현재 속도가 1~19인 경우는 고려하지않았다.

간단하게 정리해보자.

  • 객체를 상태를 가지고 있으며, 이 상태는 변경이 가능하다.
  • 객체의 상태를 변경시키는 것은 해당 객체의 행위이다.
  • 행위의 결과는 상태에 의존적이며, 상태를 이용해 표현할 수 있다.
  • 행위의 순서가 결과에 영향을 미친다.
  • 객체는 어떤 상태에 있더라도 유일하게 식별가능하다.

2.3. 객체의 세 가지 특성

객체는 식별가능한 개체 또는 사물로, 구체적인 사물일수도 추상적인 개념일 수도 있다.

소프트웨어 세계에서 객체는 저장된 상태(property)실행 가능한 코드(method) 를 통해 구현되며,

각 객체는 구별 가능한 식별자(identity), 특징적인 행위(behavior), 변경 가능한 상태(state) 를 가진다.

이 세 가지 특성을 자세히 알아보도록 하자.

2.3.1. 상태

상태는 왜 필요할까?

주변 환경과의 상호작용에 의해 객체에 어떤 변화가 발생하는 시점 A가 있다고 가정하면

시점 A에 도달하기까지 객체에 어떤 일이 발생했는지에 따라 상태가 변화해왔고, 시점 A에서 발생할 상호 작용의 결과값에도 당연히 영향을 미칠 것이다.

그렇다면 앞으로 변할 상호작용의 결과값을 계산하기 위해서 모든 행위에 대해서 기억하고 있어야할까?

만약 전부 기억해야한다면 굉장히 번거로운 작업이 될 것이다.

이 작업을 단순하게 처리하기 위해서 객체는 상태(state) 라는 개념을 사용한다.

상태를 활용하면 그동안의 행위로 계산된 결과인 현재의 상태를 기준으로 행위 결과를 다시 계산할 수 있다.

상태는 근본적으로 복잡성을 낮추고 인지 과부하를 줄여주는 중요한 역할을 수행한다.

프로퍼티(property)

모든 값을 상태로 표현할 수 있을까?

다시말해, 모든 값을 객체에서 상태로만 관리해야할까?

결론 부터 말하자면 숫자, 문자열, 속도, 시간 등의 단순한 값들은 굳이 객체화 시켜서 관리할 필요가 없다.

이 단순한 값은 그 어떠한 독립적인 의미를 가지고 있지안ㄴㅎ다.

그저 객체의 상태를 표현하기 위한 보조 수단일 뿐이다.

결론적으로 모든 객체의 상태는 단순한 값과 객체의 조합으로 표현되며, 객체의 상태를 구성하는 모든 특징을 통틀의 객체의 프로퍼티라고 한다.

일반적으로 프로퍼티는 변경되지않고 고정되므로 정적(static)이며, 이 프로퍼티에 부여되는 값(property value)는 시간에 따라 변경되므로 동적(dynamic)이다.

프로퍼티는 객체를 구성하는 단순한 값인 속성(attribute)과 다른 객체를 참조하는 링크라는 두 가지 종류의 조합으로 표현할 수 있다.

참고 링크(link)란?
객체와 객체 사이의 유의미한 연결을 링크라고 한다.
이 링크가 존재해야만 객체와 객체 사이에 요청을 주고 받을 수 있으며, 이는 객체가 다른 객체를 참조할 수 있음을,
더 정확히 객체가 다른 객체의 식별자를 알고 있다는 것을 의미한다.

이제 객체의 상태를 다시 정의해보자.

상태는 특정 시점에 객체가 가지고 있는 정보의 집합 으로 객체의 구조적 특징을 표현한다.

객체의 상태는 객체에 존재하는 정적인 프로퍼티와 동적인 프로퍼티 값 으로 구성된다.

객체의 프로퍼티는 단순한 값과 다른 객체를 참조하는 링크 로 구분할 수 있다.

2.3.2. 행위

객체는 자율적인 존재라는 점을 다시 상기해보자.

자율적인 객체는 스스로 자신의 상태를 책임져야하며, 외부의 객체가 간접적으로 상태를 변경하거나 조회할 수 있는 방법을 제공해주어야 한다.

이를 우리는 행위라고 부르며, 행위는 다른 객체로 하여금 간접적으로 객체의 상태를 변경하는 것을 가능하게 한다.

객체지향의 기본 사상은 상태와 상태를 조작하기 위한 행위를 하나의 단위로 묶는 것이며,

스스로의 행위에 의해서만 상태가 변경되는 것을 보장하여 객체의 자율성을 유지해야 한다.

이쯤에서 2.2.의 제목을 다시보자.

1
상태를 결정하는 행위, 행위의 결과를 결정하는 상태

상술했듯 우리는 이미 상태와 행위가 굉장히 밀접한 관계인 것을 알고 있는데, 이를 좀 더 명세해보도록 하자.

상태와 행위 사이에는 아래와 같은 관계가 존재한다.

  • 객체의 행위는 상태에 영향을 받는다.
  • 객체의 행위는 상태를 변경시킨다.

여기서 상태를 개념을 적용하면 객체의 행위를 아래 두 가지 관점에서 서술할 수 있게 된다.

  • 상호작용이 현재의 상태에 어떤 방식으로 의존하는가
  • 상호작용이 어떻게 현재의 상태를 변경시키는가

같은 말의 반복이지만, 현재의 상태를 이용해 과거의 행위 없이 미래의 상태를 결정짓는 것이 핵심이다.

협력과 행위

객체는 다른 객체와 메시지를 통해서 의사소통을 수행한다.

객체가 특정 행위를 수행하도록 하는 것은 객체의 외부로부터 전달된 메시지로부터 촉발된다.

이 수신된 메시지에 따라 적절한 행위를 하면서 협력에 참여하여 그 결과로 자신의 상태를 변경하는 것이다.

물론 객체 자기 자신의 상태 뿐만 아니라 다른 객체의 상태도 간접적으로 변경시킬 수 있다.

즉 객체의 행위로 인해 발생하는 결과는 아래와 같이 두 가지로 정리된다.

  • 객체 자기 자신의 상태 변경
  • 행위의 범위 안에서 협력하는 다른 객체에 대한 메시지 전송

이제 객체의 행위를 다시 정의해보자.

행위란 외부의 요청 또는 수신된 메시지에 응답하기 위해 동작하고 반응하는 활동 이다.

행위의 결과로 객체는 자신의 상태를 변경하거나 다른 객체에게 메시지를 전달 할 수 있다.

객체는 행위를 통해 다른 객체와의 협력에 참여 하므로, 이는 외부에서 식별 가능한 동작 이다.

2.3.3. 식별자

모든 객체는 식별자를 가지고 있으며, 이 식별자를 이용해 객체를 구별할 수 있다.

반대로 프로퍼티 중 단순한 값은 식별자를 가지지않고 있으며, 이는 값과 객체의 가장 큰 차이점이다.

따라서 시스템을 설계할 때는 단순한 값과 객체의 차이점을 명확하게 구분하고 명시적으로 표현하는 것이 중요하다.

값(value)은 숫자나 시간, 금액과 같이 변하지않는 양을 정량적으로 표현한다.

이 값의 상태는 변하지않기 때문에, 불변 상태(immutable state)를 가진다고 말하기도 하며,

값을 표현하는 2개의 인스턴스의 값이 같다면 이 인스턴스들은 동일한 것으로 판정한다.

이 값이 같은지 여부는 상태가 같은지를 이용해 판단하며, 값의 상태가 같으면 동일한 인스턴스로 다르면 다른 인스턴스로 판단한다.

값은 오로지 상태만을 이용해 동일한 인스턴스인지 비교하기 때문에, 별도의 식별자를 필요로 하지 않는다.

참고 상태를 이용해 두 값이 같은지 판단할 수 있는 성질을 동등성(equality) 라고 한다.

이게 객체를 생각해보자.

객체는 상태와 무관하게 두 객체를 동일하거나 다르다고 판단할 수 있는 프로퍼티를 가지고 있다.

즉, 두 객체의 상태가 다르더라도 식별자가 같다면 같은 객체라고 판정한다.

참고 식별자를 이용해 두 객체가 같은지 판단할 수 있는 성질을 동일성(identical) 라고 한다.

객체 상태의 경우 시간이 흐름에 따라 가변적으로 변하기에 동일한 객체인지 판정하는 데 사용할 수 없다.

한 쪽 객체의 상태가 변해버리는 순간, 다른 객체와 다른 객체가 되어 버리기 때문이다.

이제 객체의 식별자를 다시 정의해보자.

식별자란 어떤 객체를 다른 객체와 구분하는 데 사용하는 객체의 프로퍼티 다.

값은 식별자를 가지지않기 때문에 상태를 이용한 동등성 검사 를 통해 두 인스턴스를 비교해야 한다.

객체는 상태가 변경될 수 있기때문에 식별자를 이용한 동일성 검사 를 통해 두 인스턴스를 비교할 수 있다.

2.4. 값과 객체의 구분

소프트웨어 세계에서 값과 객체의 차이점을 구별하는 것은 혼란의 여지가 있다.

이는 객체지향 프로그래밍에서 값과 객체 모두 클래스로 이용해 구현되기 때문인데,

오해의 소지를 줄이기 위해서 값과 객체를 지칭하는 별도의 용어를 사용하기도 한다.

예를 들어 식별자를 지닌 객체를 가리키는 말로 참조 객체(reference object), 엔티티(entity) 등으로 표현하며,

값을 가리키는 말로 값 객체(VO : value object) 라고 표현하기도 한다.

일반적으로 객체라고 하는 것은 참조 객체와 엔티티를 지칭하는 경우가 많다.

2.5. 상태의 조회와 변경

객체지향 프로그래밍에서 개발자들의 주된 업무는 객체의 상태를 조회하고 객체의 상태를 변경하는 것이다.

일반적으로 객체의 상태를 조회하는 작업을 쿼리(query), 상태를 변경하는 작업을 명령(command) 이라고 한다.

즉 객체가 외부에 제공하는 행위의 대부분은 쿼리와 명령으로 구성된다.

여기서 중요한 것은 쿼리와 명령 이외에 다른 접근 방법이 없다는 것이다.

즉, 객체가 제공하는 명령과 쿼리를 통해서만 객체에 접근할 수 있다.

참고 객체지향 프로그래밍의 특징 중 하나인 캡슐화가 떠올랐다면 정답이다.

2.6. 행위가 상태를 결정해야한다

상태를 먼저 결정하고 행위를 나중에 결정하는 것은 설계에 악영향을 준다.

상태를 먼저 결정할 경우 캡슐화가 저해된다.

상태에 초점을 맞추는 경우 상태가 캡슐화가 제대로 되지 못하고 공용 인터페이스에 노출될 확률이 높아진다.

또한 객체를 협력자가 아닌 고립된 섬으로 만든다.

객체가 필요한 이유는 다른 객체와 협력하기 위해서이다.

상태를 먼저 고려하는 경우, 협력에서 벗어난 객체를 설계하게 되어 협력도가 떨어지는 객체를 만들게 된다.

협력도가 떨어지는 객체는 결국 객체의 재사용성도 저하된다.

따라서 효과적인 객체지향 설계를 위해서는 행위를 먼저 정의한 후, 이 행위를 수행할 객체를 선택하는 방식으로 수행되어야 하며,

행위를 결정한 후 필요한 정보가 무엇인지 고려하여 상태를 결정하는 것이 좋다.

2.7. 추상화

객체지향은 현실 세계에 존재하는 다양한 객체를 모방하여 소프트웨어 객체로 구현하는 과정이다.

이를 흔히 현실 세계의 추상화(abstraction) 이라고 부른다.

추상화란 사물에서 자신이 원하는 특성만 취하고, 필요없는 부분을 배제하여 핵심만 표현하는 행위를 말한다.

안타깝지만 객체지향의 세계는 현실 세계의 단순한 모방으로 그치지않는다.

우리가 해야할 일은 현실을 완벽히 모방하는 것이 아닌 새로운 객체지향의 세계를 창조하고,

현실 속의 객체를 자세히 관찰하여 이를 창조적 객체로 모방하는 것이다.

창조적 객체란, 현실 속의 제약이 없는 소프트웨어 세계에서 가능한 행위이다.