프로그래밍을 계속 하다 보면 어떤 객체지향 설계 원칙과 같은 좋은 원칙들에 대한 관심이 높아지게 된다. 객체지향 이론과 경험적 지식이 풍부한 경우에는 이미 원칙들을 잘 지켜가면서 프로그래밍을 하고 있겠지만, 아무래도 경험이 많이 쌓이기 전에는 확신을 가지고 자신의 코딩 방식이 맞는지를 알기가 어렵다. 중복 방지, 다형성, 타입 은닉 등 많은 원칙들이 있고 인터넷을 뒤져 보면 그에 대한 설명이 나오긴 하지만 실질적으로 도움을 주기 보다는 선언이나 가이드에 가까운 경우가 많다. 따라서 실제 코딩을 할 때 염두에 두고 실천할 수 있을 만큼 구체적인 가이드가 필요하다는 생각을 하게 되었다.


지금부터 이야기 할 것은 "무상태 프로그래밍"이라는 실천 원칙이다. 읽어 보다 보면 이 원칙이 왜 나오게 되었는지, 다른 객체지향 설계 원칙과의 관계가 어떻게 되는지 알게 될 것이다.


이 이론에는 몇가지 전제가 있다.


1. 객체지향의 구성 요소는 타입과 객체 참조 그리고 행위이다. 속성은 객체지향의 핵심 구성 요소가 아니다.

2. 속성이 상태를 만든다.

3. 이상적인 객체 세계는 무상태이다.

4. 상태는 객체 세계와 외부 세계간의 소통을 위해서만 발생한다.


속성은 객체지향의 구성 요소가 아니다

속성(쉽게 말해서 변수)은 비 객체지향 언어로부터 파생된 산물이다.

객체지향의 저변 확대를 위해 포용한 비 객체지향적 요소이다.

모든 속성은 객체로 치환이 가능하다.


속성이 객체의 세계에서 얼마나 이질적인 것인지 한번 생각해 보자. Java를 통해 이야기 해보자.


1. List<Integer>는 있지만 List<int>는 없다.


List는 객체이다. List<int>는 왜 없을까? 답은 List가 객체를 다루도록 만들어진 인터페이스이기 때문이다. 그러면 왜 primitive 타입은 다루지 않을까? primitive 타입을 다루려면 조건문을 통해 별도로 처리해 주어야 하기 때문이다. 객체가 아닌 이상 조건문 없이 객체를 다루는 코드로 primitive 타입을 사용할 수 없다. 이것은 단일 책임 원칙을 위반하는 것이다.


2. equals가 없다.


primitive 타입은 동치성 계산을 위해서 equals() 함수를 사용할 수 없다. Java에서는 primitive 타입의 동치성을 판단할 수 있는 방법이 없다.


3. 행위가 없다.


가장 문제 삼아야 하는 부분이다. 속성은 행위를 가지고 있지 않다. 속성이 행위하지 않기 때문에 다른 곳에서 속성의 행위를 대신 해주어야 한다. 속성이 하지 못하는 일을 대신 해주는 것이 문제를 일으킨다.


4. 객체가 대신 할 수 있다.


int 타입을 가지고 하는 일은 Integer 객체가 할 수 있는 일이다. 실제로는 Java에서 primitive 타입을 모두 객체로 구현해 두고 있고, 필요하다면 모두 객체로 선언하는 것도 가능하다.


속성이 상태를 만든다

속성은 변화되는 값을 가진다. 변화되는 값은 소프트웨어에 위험 요소가 된다. C언어에서 전역 변수를 생각해 보자. 전역 변수가 가진 값은 변화한다. 이 변화는 어느 함수를 통해 일어날지 예측이 불가능하다. 따라서 위험하다. 그래서 객체지향에서는 변수, 즉 속성을 객체 안에서만 사용하도록 만들었다. 그러면 모든 문제가 해결되었을까? 당연히 아니다. 전역 변수 역할을 하는 것을 목적으로 싱글톤을 만들기도 하고, 다른 객체에게 자기가 가지고 있던 속성을 매개변수로 넘기기도 한다. 이런 몇몇의 행위들 때문에 전역 변수를 없애려고 했던 노력이 무색해 지기도 한다.


속성은 변화되는 값을 가진다. 하지만 스스로 행위하지는 못한다. 객체는 다른 객체의 행위에 의해 자신의 행위를 변화시키지만 속성은 변화하되 행위는 하지 못한다. 그래서 행위를 시킨다. if(state == X) 이면 A, 아니면 B를 하라 라고 시킨다. 이런 속성, 즉 값을 가지는 것에 머물지 않고 행위를 변경하는 속성을 상태라고 한다.


상태는 번식하는 특성이 있다. 한번 속성이 "상태"라고 인식되는 순간, 한번 쓰였던 조건 문은 클래스 내에서 종종 다시 쓰인다. 만일 한 클래스 내에 상태가 두 개 이상이 되고, 이들이 서로 뒤엉켜 사용되면 조건문은 중첩으로 발생한다.


자 따라가 보면 이렇다. 속성이 상태를 만든다. 상태는 행위를 변경시킨다. 행위를 변경시키기 위해 조건문을 만든다. 조건문은 조건문의 번식과 중첩 조건문을 만든다. 조건문은 가독성을 떨어뜨린다.


애초에 이런 일이 발생하지 않도록 하기 위해서는 속성을 만들지 않는 것이 좋다.


이상적인 객체 세계는 무상태이다

인터페이스는 상태가 없다. 객체와 객체 간에는 상태가 존재하지 않는다. 따라서 어떤 객체가 다른 객체의 상태를 살필 필요도 없다. 그러면 객체 내부에서는 어떻게 해야 할까? 이 쯤에서 인정해야 할 것은 속성은 필수 불가결하다는 점이다. 어쨌든 속성은 필요하다. 다만 속성이 상태화 하는 것을 막아야 한다는 것이다. 어떻게? 속성을 객체로 만들어 버리는 것이다. 그리고 속성을 통해 변경하고자 했던 행위를 그 객체의 행위로 변경한다. 이런 방식으로 상태를 없애버린다. 속성의 변경은 객체에 대한 변경으로 치환한다. 객체와 객체와의 관계에서 상태는 존재하지 않기 때문에 이것은 충분히 가능하다.


상태는 객체 세계와 외부 세계간의 소통을 위해서만 발생한다

구체적인 방법은 다음 글에서 설명하겠다. 일단 상태가 어쩔 수 없이 발생하는 단 한가지 원인을 말해야 한다. 그것은 객체의 세계와 비 객체의 세계가 소통할 때 발생한다. 즉, 객체 지향으로 만들어진 소프트웨어와 연동되는 다른 시스템(예를 들어 통신을 통한 시스템 연동, 데이터 베이스, UI 등)에서는 상태 값을 객체로 만들어 전달해 줄 수 없다. 따라서 객체의 세계에서는 이를 어쩔 수 없이 값을 받아야 한다.

Posted by 이세영2
,