객체 지향의 사실과 오해 #2 (이상한 나라의 객체)

임현규·2022년 12월 24일
0

객체지향 패러다임은 지식을 추상화하고 추상화한 지식을 객체 안에 캡슐화함으로써 실세계 문제에 내재된 복잡성을 관리한다. - 레베카 워프스브룩

이상한 나라와 객체지향

책에서는 이상한 나라의 앨리스 이야기의 일부분을 통해 객체 지향에 대해서 설명하고 있다. 이야기를 요약하면 다음과 같다.


엘리스는 아름다운 정원에 들어가야 한다. 하지만 정원의 문은 작아서 통과할 수 없다.
버섯의 한 부분을 먹으면 키가 커지고 다른 부분을 먹으면 작아진다. 탁자 위에 열쇠가 있는데 탁자가 너무 높아 버섯의 한 부분을 먹어 키를 키워서 열쇠를 집고, 다른 한 부분을 먹어서 키를 작게 만들어 문을 통과했다. 마침내, 그녀는 아름다운 정원에 들어갔다.


상태와 행동의 중요성

이를 객체지향으로 해석하면 다음과 같다. (책에선 엘리스와 음료로 비교한다)

  • 엘리스
    • 상태: 키(m)
    • 행동: 버섯을 먹다
  • 버섯
    • 상태: 양(g)
    • 행동: 버섯의 무게가 줄어듬

객체지향으로 설계하기 위해서 현실의 문제를 상태와 행위로 객체를 정의하고 있다. 왜 2개를 정의하는 것일까? 더 나아가 상태와 행동이 왜 중요한 것일까?

자판기에 동전을 넣어 음료수를 뽑아 먹으려 한다. 만약 내가 가진 돈이 충분하다면 음료수를 먹을 수 있지만 그렇지 않다면 음료수를 먹을 수 없다. 여기서 나라는 객체는 음료수를 자판기에게 요청하는 행동을 할 때 나의 지갑 상태에 따라 결과가 결정 된다는 것이다. 행동은 상태를 변경하고 최종적으로 우리는 상태를 통해 행동의 결과를 판단한다. 그렇기 때문에 이 둘을 정의하는 것은 굉장히 중요하다.

객체 VS 값

예제에서 엘리스와 버섯을 객체로 표현했지만 그 외에도 객체가 존재할 수 있다. 반대로 모든 특징들이 객체가 되는 것은 아니다. 예를 들면 엘리스의 키, 그리고 버섯의 양이다. 이는 단순한 수치로 현재 상태에 따라 식별한다. 보통 IT 계에서는 이를 값 객체(Value Object)라 한다. 또한 우리가 말하는 객체라는 것은 보통 참조 객체(Reference Object) 또는 엔티티(entity)로 불린다.

앨리스의 키를 키우거나 줄여주는 버섯, 그리고 버섯의 양, 엘리스의 키 모두 프로퍼티가 될 수 있다. 이 때 정적인 프로퍼티를 속성, 동적인 프로퍼티를 링크로 정의한다. 이때 정적인 프로퍼티는 보통 Value Object이고 동적인 프로퍼티는 참조 객체나 엔티티가 될 수 있다.

엘리스를 살펴보자.. 엘리스가 버섯을 가지고 있는 상태인지 가지고 있지 않은 상태인지 알고 싶고, 엘리스가 버섯을 먹는 행위에 따라서 버섯에게 상태 변경을 요청한다고 가정하자.. 이 때 우리는 엘리스라는 객체의 프로퍼티를 다음과 같이 구성할 수 있다.

  • 엘리스의 프로퍼티
    • 속성: 키(m)
    • 연관관계: 버섯
  • 버섯의 프로퍼티
    • 버섯의 양(g)

이렇게 엘리스와 버섯을 링크해놓으면 엘리스가 버섯을 가지고 있는지에 대한 여부를 알 수 있다.

상태 캡슐화와 행동

엘리스의 프로퍼티인 버섯의 양을 줄이고 싶지만 객체이기 때문에 버섯에게 버섯 욤뇸뇸 이라는 행동을 버섯에게 전달한다. 버섯은 이를 듣고 버섯 양 줄여 라는 행동을 수행하고, 결국 버섯의 양은 줄어든다. 그와 동시에 엘리스의 키는 커지거나, 줄어든다.

예제에서 뽑아낸 객체의 행동과 상태는 현실과 다르게 이상한 점이 있다. 현실에서의 엘리스라면 엘리스가 버섯을 먹는 행위를 수행했고 그로 인해 버섯의 양이 줄었다. 그러나 객체지향에서는 객체의 상태를 외부 상태가 침범하면 캡슐화가 깨진다. 그리고 버섯이라는 객체의 의미가 없어진다.

객체지향에서는 상태를 객체 스스로 관리하고, 객체가 다른 객체의 상태의 변경을 원한다면 다른 객체에게 상태 변경을 요청해야 한다. 마치 엘리스라는 사람이 버섯이라는 사람에게 상태를 변경하도록 요청하는 것이다. 이 때문에 객체지향에서 객체를 살아있는 생물에 빗대어 표현하기도 한다.

코드로 해석해보기

class Person {

    private static final String NOT_NULL_ERROR = "사람의 이름과 키는 Null값을 허용하지 않아요!!";

    private final Name name;
    private Height height;
    private Mushroom mushroom;

    public Person(Name name, Height height, Mushroom mushroom) {
        validateNullProperty(name, height);
        this.name = name;
        this.height = height;
        this.mushroom = mushroom;
    }

    public void eatMushroom() {
        this.mushroom.reduceAmount();
        this.height = height.plus(20);
    }

    private static void validateNullProperty(Name name, Height height) {
        if (name == null || height == null) {
            throw new IllegalArgumentException(NOT_NULL_ERROR);
        }
    }
    // equals hashcode => name
}

class Mushroom {

    private static final String NOT_NULL_ERROR = "버섯의 이름은 Null값을 허용하지 않아요!!";

    private final Name name;
    private Amount amount;

    public Mushroom(Name name, Amount amount) {
        validateNullProperty(name, amount);
        this.name = name;
        this.amount = amount;
    }

    public void reduceAmount() {
        this.amount = amount.reduce(10);
    }

    private static void validateNullProperty(Name name, Amount amount) {
        if (name == null || amount == null) {
            throw new IllegalArgumentException(NOT_NULL_ERROR);
        }
    }

    // equals hashcode => name
}

공부한 내용을 코드로 표현해 보았다.

여기서 엘리스 객체를 Person으로 둔 이유는 엘리스 라는 이름을 객체의 식별자로 쓰기 위함이다.

profile
엘 프사이 콩그루

0개의 댓글