이상한 나라 앨리스 코드로 본 객체의 의미
Table of Contents
협력하는 객체들의 공동체#
객체란 ?#
세상 속에서 사람들은 각자의 역할에 책임을 다하며 협력한다. 비슷하게, 객체들도 하나의 프로그램 안에서 기능을 구현하기 위해 역할을 맡고, 책임을 다하고, 협력한다. 하나의 객체는 다른 객체에게 메세지를 보내서 요청을 할 수 있고, 요청받은 객체는 자신의 메서드를 사용하여 요청을 처리한다.
객체지향 설계는, 적절한 객체에게 적절한 책임을 할당하는 것이다. 객체는 다음과 같은 특징을 가진다.
- 여러 객체가 동일한 역할을 수행할 수 있다.
- 역할은 대체 가능성을 의미한다.
- 각 객체는 책임을 다하는 방법을 자율적으로 선택할 수 있다.
- 하나의 객체가 동시에 여러 역할을 수행할 수 있다.
객체는 서로 협력하며 하나의 기능을 완성하기 위해 노력한다. 따라서, 객체는 충분히 ‘협력적’ 이어야 한다. 다른 객체가 요청을 하면 충실하게 요청을 이행해야 한다. 두번째로, 객체는 충분히 ‘자율적’이어야 한다. 받은 요청을 처리하는 방식은 온전히 그 객체한테 맡겨야 한다.
객체를 상태(state) 와 행동(behavior) 을 함께 지닌 실체라고 정의한다. 객체의 자율성은 객체의 내부와 외부를 명확하게 구분하는 데서 시작한다. 즉, 외부에서 객체가 무엇(what)을 하는지는 알 수 있지만, 어떻게(how) 는 몰라야 한다. 요청받은 사항을 처리하는 것을 메서드(method)라고 한다. 외부의 요청이 무엇인지 표현하는 메서드와, 요청을 처리하는 구체적인 메서드를 분리해야 객체의 자율성을 높일 수 있다. 이것은 캡슐화(encapsulation) 와도 깊은 관련이 있다.
객체지향의 본질#
- 객체지향이란 시스템을 상호작용하는 자율적인 객체들의 공동체로 바라보고, 객체들을 이용해 시스템을 분할하는 방법이다.
- 자율적인 객체란 상태와 행위를 함께 지니며, 스스로 자기 자신을 책임지는 객체이다.
- 객체는 시스템의 행위를 구현하기 위해 다른 객체와 협력한다. 각 객체는 협력 내에서 정해진 역할을 수행하며 역할은 관련된 책임의 집합이다.
- 객체는 다른 객체와 협력하기 위해 메세지를 전송하고, 메세지를 수신한 객체는 메세지를 처리하는 데 적합한 메서드를 자율적으로 선택한다.
나도 그동안 객체지향 = class 로 생각했었다. 하지만 이것은 크나큰 오해이다. 사실 클래스는, 협력에 참여하는 객체들을 만드는 데 필요한 구현 메카니즘이다. 핵심적인 것은 객체의 역할, 책임, 협력이다 !
이상한 나라의 앨리스 세계로 본 객체지향#
이상한 나라의 앨리스를 이용해 객체지향을 실습해보자. 우선, Alice 객체를 만들어보자. 앨리스는 음료수를 마시면, 키가 줄어든다. 즉, 앨리스의 상태를 변화시키는 것은 앨리스의 행동이다. 또, 행동의 결과를 결정하는 것은 상태이다. 음료수를 마시기 전 앨리스의 키가 160이고, 50의 음료수를 마신다면, 그 후 앨리스의 키는 110이다. 즉 앨리스가 한 행동의 결과는 앨리스의 상태에 의존적이다.
객체, 그리고 소프트웨어 나라#
객체는 상태(state), 행동(behavior), 식별자(identity) 를 지닌 실체이다. 각 개념에 대해 더 알아보자.
상태#
어떤 행동의 결과는 과거에 어떤 행동들이 일어났었느냐에 의존한다. 하지만 과거에 일어났던 모든 행동들을 보는 것은 번거롭다. 그래서 이것을 ‘상태’ 라는 개념으로 나타내기로 했다. 객체의 상태를 표현하는 것을 프로퍼티(property) 라고 한다.
만약 앨리스가 음료수르 들고 있는 상태를 나타내려면 어떻게 해야할까 ? 앨리스의 상태 일부를 음료라는 객체를 사용해서 표현하면 된다. 즉, 앨리스 객체와 음료 객체는 링크를 통해 연결되어 있음을 나타내면 된다. 객체의 링크를 통해서 메세지를 주고받을 수 있다.
객체의 상태는 객체가 가지고 있는 정보의 집합으로, 객체의 구조적 특징을 표현한다. 객체의 상태는 객체에 존재하는 정적인 프로퍼티와 동적인 프로퍼티 값으로 구성된다. 객체의 프로퍼티는 단순한 값과 다른 객체를 참조하는 링크로 구분할 수 있다.
행동#
행동은 다른 객체로 하여금 간접적으로 객체의 상태를 변경할 수 있게 해준다.
- 객체의 행동은 상태에 영향을 받는다.
- 객체의 행동은 상태를 변화시킨다.
행동이란 외부의 요청 또는 수신된 메세지에 응답하기 위해 동작하고 반응하는 행동이다. 행동의 결과로 객체는 자신의 상태를 변경하거나 다른 객체에게 메세지를 전달할 수 있다. 객체는 행동을 통해 다른 객체와의 협력에 참여하므로 행동은 외부에 가시적이어야 한다.
외부에 노출되는 것은 행동이지, 상태가 아니다. 따라서 행동을 경계로 캡슐화해야 한다. 객체에 접근할 수 있는 유일한 방법은 객체가 제공하는 행동뿐이다.
식별자#
객체가 식별 가능하다는 것은, 객체를 서로 구별할 수 있는 프로퍼티가 객체 안에 존재한다는 것이다. 이 프로퍼티를 식별자라고 한다.
행동이 상태를 결정한다#
객체지향을 설계할 때, 행동이 상태를 결정하도록 모델링해야 한다. 협력 안에서 객체의 행동은 객체가 협력에 참여하면서 해내야 하는 책임을 의미한다. 따라서 어떤 책임이 필요한가를 결정하는 과정이 전체 설계를 주도해야 한다. 이것을 책임-주도 설계(Responsibility-Driven Design) 이라고 한다.
코드로 살펴보기#
Alice, Drink 두가지 객체를 만들어보자. Alice 가 음료수를 마시면, Drink 객체에게 메세지를 보낸다. 그러면 Drink 객체 내부의 메서드로, 음료수의 양이 줄어들게 한다.
public class Alice {
int height;
public Alice(int height) {
this.height = height;
}
public void drinkBeverage(Drink beverage, int amount) {
beverage.drunken(amount);
height -= amount;
}
}
public class Drink {
int quantity;
public Drink(int quantity){
this.quantity = quantity;
}
public void drunken(int amount){
quantity -= amount;
}
}
Alice 객체의 drinkBeverage 라는 메서드에서, Drink 객체의 인스턴스인 beverage 에게 drunken 이라는 메서드를 실행하라는 요청을 보낸다. 그러면, Drink 객체에서 amount 만큼 자신의 프로퍼티인 quantity 가 줄어들게 한다. 그 후, Alice 객체는 자신의 height 를 줄인다.
타입과 추상화#
객체지향과 추상화#
추상화/구체화는 인간의 지각에서도 굉장히 중요한 요소이다. 우리는 말티즈, 치와와, 허스키, 진돗개 .. 를 통틀어서 공통적인 속성을 추출해내 ‘개’ 라고 인지한다. 이 다양한 개체들을 ‘개’ 라고 명명할 수 있는 이유는 ‘개’ 라고 했을 때 떠오르는 일반적인 외형과 행동 방식을 가지고 있기 때문이다. 샴고양이는 이러한 ‘개’ 의 일반적인 행동방식을 가지고 있지 않으므로 ‘개’에 속하지 않는다.
이처럼 공통점을 기반으로 객체들을 묶기 위한 그릇이 개념(concept) 이다. 개념을 이용하면 객체를 여러 그룹으로 분류(classification) 할 수 있다. 어떤 개념에 속하는 특정 개체를 인스턴스(instance) 라고 한다.
개념에는 3가지 관점이 있다.
- 심볼(symbol) : 개념을 가리키는 이름
- 내연(intension) : 개념의 완전한 정의를 나타내며, 내연의 의미를 이용해 객체가 개념에 속하는지 여부를 확인할 수 있다.
- 외연(extension) : 개념에 속하는 모든 객체의 집합(set)
심볼은 개념의 이름으로, 위의 예시로는 ‘개’ 가 될 것이다. ‘내연’ 이란 개념의 의미이다. 내연은 개념을 객체에게 적용할 수 있는지 여부를 나타낸다. 샴고양이는 개의 내연을 만족하지 못하기 때문에 개가 될 수 없다. 외연은 개념에 속하는 객체들, 인스턴스들의 모여 이루어진 집합이다.
타입#
타입의 정의는 개념과 동일하다. 다만 컴퓨터의 세계에 더 맞춰진 용어이다.
- 타입은 데이터가 어떻게 사용되느냐에 관한 것이다.
- 타입에 속한 데이터를 메모리에 어떻게 표현하는지는 외부로부터 철저하게 감춰진다. 여기서 데이터 타입에 대한 정의가 나온다.
데이터 타입은 메모리 안에 저장된 데이터의 종류를 분류하는데 사용하는 메모리 집합에 대한 메타데이터이다. 데이터에 대한 분류는 암시적으로 어떤 종류의 연산이 해당 데이터에 대해 수행될 수 있는지를 결정한다.
타입의 계층#
다시 이상한 나라의 앨리스로 돌아가보자. 여기서 트럼프카드가 나온다. 트럼프인간는 트럼프이면서, 먹고 걸을 수 있다. 트럼프 - 트럼프인간의 관계를 일반화/특수화 관계라고 한다. 타입과 타입 사이에는 일반화/특수화 관계가 적용될 수 있다. 트럼프인간은 트럼프의 특수한 개념이다. 즉, 일반적인 트럼프의 개념보다 범위가 더 좁고, 속하는 객체의 수가 더 적어진다. 여기서 중요한 것은 일반화/특수화 관계를 결정하는 것은 데이터가 아니라 행동이라는 것이다. 트럼프인간는 트럼프보다 더 많은 행동을 할 수 있기 때문에 특수화된 타입이다. 여기서 일반화된 타입(트럼프)를 슈퍼타입(supertype), 특수화된 타입(트럼프인간)을 서브타입(subtype) 이라고 한다.
java 코드로 보자. Trump 을 인터페이스로 설계하고, TrumpKing 은 Trump 를 implement 하도록 했다. 하지만 supertype/subtype 를 관계를 나타내는 방법은 훨씬 많다. Trump 는 canFlip 행동을 할 수 있다. TrumpKing 은 canFlip 는 당연히 할 수 있고, ruleTheWorld() 행동도 할 수 있다. 다른 subtype 인 TrumpQueen 은 openTeaParty 행동도 할 수 있다.
public interface Trump {
void canFlip();
}
public class TrumpKing implements Trump{
@Override
public void canFlip() {
System.out.println("TrumpKing can flip!");
}
public void ruleTheWorld() {
System.out.println("TrumpKing rules the world!");
}
}
public class TrumpQueen implements Trump{
@Override
public void canFlip() {
System.out.println("TrumpQueen can flip!");
}
public void openTeaParty() {
System.out.println("TrumpQueen can open tea party!");
}
}
클래스는 타입을 구현하는 방법 중 하나일 뿐이다. 클래스와 타입이 동일한 것은 아니다 !
Reference#
- 객체지향의 사실과 오해, ch.1,2,3