티스토리 뷰

728x90
SMALL

책내용

1. 객체지향과 인지 능력

인간은 본능적으로 세상을 독립적이고 식별 가능한 객체의 집합으로 바라본다. 많은 사람들이 객체지향을 직관적이고 이해하기 쉬운 패러다임이라고 말하는 이유는 객체지향이 세상을 자율적이고 독립적인 객체들로 분해할 수 있는 인간의 기본적인 인지 능력에 기반을 두고 있기 때문이다.

세상을 더 작은 객체로 분해하는 것은 본질적으로 세상이 포함하고 있는 복잡성을 극복하기 위한 인간의 작은 몸부림이다. 인간은 좀 더 단순한 객체들로 주변을 분해함으로써 자신이 몸담고 있는 세상을 이해하려고 노력한다. 즉, 객체란 인간이 분명하게 인지하고 구별할 수 있는 물리적인 또는 개념적인 경계를 지닌 어떤 것이다.

객체지향 패러다임의 목적은 현실 세계를 모방하는 것이 아니라 현실 세계를 기반으로 새로운 세계를 창조하는 것이다.

2. 객체, 그리고 이상한 나라

이상한 나라의 앨리스를 읽어 봤는가? 솔직히 너무 오래되서 나는 잘 기억이 나지 않았다. 그래서 간단하게 앞부분 줄거리를 소개하겠다.

앨리스는 뛰어가는 토끼를 쫓아 굴 속으로 들어간다. 그리고 내부를 둘러보다가 작은 문을 발견한다. 그 문 뒤에는 아름다운 정원이 펼쳐져 있었다. 하지만 문이 작았기 때문에 들어갈 수 없었다. 병속의 용액을 먹으니 키가 커졌고 케이크를 먹었더니 키가 작아졌다. 그러다가 벌레로부터 버섯을 얻었는데 그 벌레는 버섯의 한쪽을 먹으면 몸이 커지고 다른 한쪽을 먹으면 몸이 작아진다고 했다. 그렇게 몇번을 반복하다가 앨리스는 문에 들어갈 수 있게 되었다.

앨리스의 키는 시간의 흐름에 따라 변한다. 정확히는 버섯을 먹을 때 변한다.

즉 앨리스의 상태(키)를 결정하는 것은 행동(버섯)이지만 행동의 결과를 결정하는 것은 상태다. 앨리스가 한 행동의 결과는 앨리스의 상태에 의존적이다.(버섯의 한쪽을 먹었을 때 10cm가 커진다고 가정하고 현재 키가 100cm라면 110cm가 된다.)

상태에 따라 행동의 결과가 달라지는 또 다른 예로 문을 통과하는 장면을 들 수 있다.

앨리스의 특징을 요약해보자

  • 앨리스는 상태를 가지며 상태는 변경 가능하다.
  • 앨리스의 상태를 변경 시키는 것은 앨리스의 행동이다.
    • 행동의 결과는 상태에 의존적이며 상태를 이용해 서술할 수 있다.
    • 행동의 순서가 결과에 영향을 미친다.
  • 앨리스는 어떤 상태에 있더라도 유일하게 식별 가능하다.

3. 객체, 그리고 소프트웨어 나라

객체의 다양한 특성을 효과적으로 설명하기 위해서는 객체를 상태(state), 행동(behavior), 식별자(identity)를 지닌 실체로 보는 것이 가장 효과적이다.

책에서는 다음과 같이 객체를 정의한다.

객체란 식별 가능한 개체 도는 사물이다. 객체는 자동차처럼 만질 수 있는 구체적인 사물일 수도 있고, 시간처럼 추상적인 개념일 수도 있다. 객체는 구별 가능한 식별자, 특징적인 행동, 변경 가능한 상태를 가진다. 소프트웨어 안에서 객체는 저장된 상태와 실행 가능한 코드를 통해 구현된다.

상태

객체가 주변 환경과의 상호작용에 어떻게 반응하는가는 그 시점까지 객채에 어떤 일이 발생했느냐에 좌우된다. 앨리스가 버섯을 어떻게 먹었는지에 따라 키가 달라지는 것처럼 말이다. 그런데 어떤 일이 발생했는지 모두 기억하는 것은 너무 어렵다. 그래서 인간은 행동의 과정과 결과를 단순하게 기술하기 위해 상태라는 개념을 고안했다. 즉, 앨리스의 키와 문의 높이라는 두 가지 상태만 알면 문을 통과하는 행동의 결과를 쉽게 예측할 수 있게 된다.
상태를 이용하면 과거에 얽매이지 않고 현재를 기반으로 객체의 행동 방식을 이해할 수 있다.

상태와 프로퍼티

앨리스, 토끼, 음료, 케이크 모두 객체다. 그런데 모든 것들이 객체는 아니다. 예를 들어 앨리스의 '키'와 '위치'는 객체가 아니다.
앨리스의 상태는 키와 위치라는 단순한 값과 음료라는 객체의 조합으로 표현할 수 있다.
결론적으로 모든 객체의 상태는 단순한 값과 객체의 조합으로 표현할 수 있다. 이때 객체의 상태를 구성하는 모든 특징을 통틀어 객체의 프로퍼티라고 한다. 일반적으로 프로퍼티는 변경되지 않기 때문에 '정적'이지만 프로퍼티 값은 시간이 흐름에 따라 변경되기 때문에 동적이다.
앨리스가 정원으로 들어가고 음료를 버렸다고 가정해보자. 이때 앨리스는 음료에 관해 알지 못한다. 이렇게 객체 사이의 의미있는 연결을 링크라고 한다.
링크와 달리 객체를 구성하는 단순한 값은 속성(attribute)라고 한다.

이책에서는 객체의 상태를 다음과 같이 정의한다.

상태는 특정 시점에 객체가 가지고 있는 정보의 집합으로 객체의 구조적 특징을 표현한다. 객체의 상태는 객체에 존재하는 정적인 프로퍼티와 동적인 프로퍼티 값으로 구성된다. 객체의 프로퍼티는 단순한 값과 다른 객체를 참조하는 링크로 구분할 수 있다. 객체의 프로퍼티는 단순한 값과 다른 객체를 참조하는 링크로 구분할 수 있다.

행동

객체의 상태는 저절로 변경되지 않는다. 객체가 상태를 변경하는 것은 객체의 자발적인 행동뿐이다. 객체의 행동에 의해 객체의 상태가 변경된다는 것은 행동이 부수 효과(side effect)를 초래한다는 것을 의미한다.

상태와 행동 사이의 관계

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

행동

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

협력과 행동
객체는 협력에 참여하는 과정에서 자기 자신의 상태뿐만 아니라 다른 객체의 상태 변경을 유발할 수도 있다. 예를들어 앨리스가 음료를 마시면 앨리스 자신의 키가 작아지는 동시에 앨리스가 먹은 양만큼 음료의 양이 줄어야 한다.

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

이 책에서는 행동을 다음과 같이 정의한다.

행동이란 외부의 요청 또는 수신된 메시지에 응답하기 위해 동작하고 반응하는 활동이다. 행동의 결과로 객체는 자신의 상태를 변경하거나 다른 객체에게 메시지를 전달할 수 있다. 객체는 행동을 통해 다른 객체와의 협력에 참여하므로 행동은 외부에 가시적이어야 한다.

상태 캡슐화
현실에서는 앨리스는 음료를 마시는 능동적인 존재지만 음료는 스스로는 아무것도 할 수 없는 수동적인 존재다. 그러나 객체지향의 세계에서 모든 객체는 자신의 상태를 스스로 관리하는 자율적인 존재다.

drinkBeverage() => 앨리스 => drunken(quantity) => 음료

위의 메시지를 보고 앨리스의 키가 줄어든다거나 음료의 양이 줄어든다는 상태변경을 예상할 수 없다. 이게 캡슐화다. 객체는 상태를 캡슐 안에 감춰둔 채 외부로 노출하지 않는다. 객체가 외부에 노출하는 것은 행동뿐이며, 외부에서 객체에 접근할 수 있는 유일한 방법 역시 행동뿐이다. 그리고 객체에게 메세지를 전달하는 외부의 객체는 메시지를 수신하는 객체의 상태가 변경된다는 사실조차 알지 못한다.
상태를 외부에 노출시키지 않고 행동을 경계로 캡슐화하는 것은 결과적으로 객체의 자율성을 높인다.
결론적으로 상태를 잘 정의된 행동 집합 뒤로 캡슐화하는 것은 객체의 자율성을 높이고 협력을 단순하고 유연하게 만든다.

식별자

객체의 프로퍼티를 식별자라고 한다. 모든 객체는 식별자를 가지며 식별자를 이용해 객체를 구별할 수 있다. 즉 객체가 아닌 단순한 값은 식별자를 가지지 않는다.

값(value)은 변하지 않는 불변 상태(immutable state)를 가진다. 두 인스턴스 상태가 같다면 두 인스턴스를 같다고 판단한다.(동등성(equality))
객체는 시간에 따라 변경되는 상태를 포함하며, 행동을 통해 상태를 변경한다. 따라서 객체는 가변 상태(mutable state)를 가진다. 객체의 상태가 완전히 똑같더라도 두 객체는 독립적인 별개의 객체이다.(동일성(identical))

상태가 가변적인 두 객체의 동일성을 판단하기 위해서는 상태 변경에 독립적인 별도의 식별자를 이용할 수밖에 없다.

식별자란 어떤 객체를 다른 객체와 구분하는데 사용하는 객체의 프로퍼티다. 값은 식별자를 가지지 않기 때문에 상태를 이용한 동등성 검사를 통해 두 인스턴스를 비교해야 한다. 객체는 상태가 변경될 수 있기 때문에 식별자를 이용한 동일성 검사를 통해 두 인스턴스를 비교할 수 있다.

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

4. 기계로서의 객체

객체지향의 세계를 창조한느 개발자들의 주된 업무는 객체의 상태를 조회하고 객체의 상태를 변경하는 것이다.
쿼리(query) : 객체의 상태를 조회하는 작업
명령(command) : 객체의 상태를 변경하는 작업

버트란드 마이어는 객체를 기계에 비유해서 설명한다.
이상한나라의 앨리스 이야기를 기계에 넣어보자.

위의 그림에서 네 개의 사각형 버튼을 누르면 기계의 상태가 변경된다. 그리고 동그라미 버튼을 누르면 앨리스의 상태를 출력한다. 이는 객체의 행동을 유발하기 위해 메시지를 전송하는 것과 유사하다. 버튼을 누르는 것은 기계의 사용자지만 눌린 버튼에 따라 어떤 방식으로 동작할지는 기계 스스로 결정한다. 이것은 전달된 메시지에 따라 스스로 판단하고 결정하는 자율적인 객체의 특성을 정확하게 묘사한다.

캡슐화
여기서 중요한 것은 명령 버튼과 쿼리 버튼 이외의 다른 버튼을 통해서는 앨리스 기계를 사용할 수 없다는 것이다.

식별자
두 개의 객체의 디스플레이 창에 동일한 130을 표시하고 있다고 해도 두 기계는 별개의 객체이니 말이다.
객체 간의 메시지를 통한

협력
한 객체에는 앨리스를, 다른 기계에는 음료 객체를 표현하자. 이때 두 기계를 연결시켜서 앨리스 기계의 '음료를 마시다' 버튼을 눌렀을 때 음료 기계에게 '마셔지다'라는 버튼이 눌러지게 요청을 전송하면 된다.

5. 행동이 상태를 결정한다

객체지향의 함정 중 하나는 상태를 중심으로 객체를 바라보는 것이다. 이렇게되면 설계에 나쁜 영향을 끼친다.

  1. 상태를 먼저 결정할 경우 캡슐화가 저해된다.
  2. 객체를 협력자가 아닌 고립된 섬으로 만든다.
  3. 객체의 재사용성이 저하된다.

행동이 상태를 결정한다.

6. 은유와 객체

1장에서는 클래스가 객체지향의 핵심이라는 객체지향의 오래된 도시전설 한 가지를 살펴봤다. 이번에는 '객체지향이란 현실 세계의 모방'이라는 도시전설을 살펴보자.

흔히 객체지향을 현실 세계의 추상화라고 하는데, 그 안에는 현실 세계를 모방해서 단순화한다는 의미가 숨어있다. 여기서 추상화란 실제의 사물에서 자신이 원하는 특성만 취하고 필요 없는 부분을 추려 핵심만 표현하는 행위를 말한다. 그러나 객체지향 세계는 단순한 모방이 아니다. 현실과 다르다.

의인화
현실 속의 객체와 소프트웨어 객체 사이의 가장 큰 차이점은 현실 속에서는 수동적인 존재가 소프트웨어 객체로 구현될 때는 능동적으로 변한다는 것이다.
레베카 워프스브록은 현실의 객체보다 더 많은 일을 할 수 있는 소프트웨어 객체의 특징을 의인화라고 부른다.

은유
현실 세계와 객체지향 세계 사이의 관계를 좀 더 정확하게 설명할 수 있는 단어는 은유다.
은유 : 실제로는 적용되지 않는 한 가지 개념을 이용해 다른 개념을 서술하는 대화의 한 형태

은유는 표현적 차이 또는 의미적 차이라는 논점과 관련성이 깊다. 여기서 차이란 소프트웨어에 대한 사람들이 생각하는 모습과 실제 소프트웨어의 표현 사이의 차이를 의미한다. 은유 관계에 있는 실제 객체의 이름을 소프트웨어 객체의 이름으로 사용하면 표현적 차이를 줄여 소프트웨어의 구조를 쉽게 예측할 수 있다. 따라서 소프트웨어 객체에 대한 현실 객체의 은유를 효과적으로 사용할 경우 표현적 차이를 줄일 수 있으며, 이해하기 쉽고 유지보수가 용이한 소프트웨어를 만들 수 있다.

위 그림처럼 이상나라의 객체는 현실의 객체에 대한 은유를 기반으로 한다.

우리의 목적은 현실을 모방하는 것이 아니라 우리만의 세계를 창조하는 것이다.

느낀점

1장의 커피 공화국도 괜찮은 예시라고 생각했는데 이상한 나라의 앨리스는 더욱더 좋은 예시라는 생각이든다. 1장에서도 계속 하던 얘기가 '객체자형은 현실 세계를 모방한 것이 아니다'인데 이 부분에 대해 더욱 상세하게 얘기해줘서 좋았던 것 같다. 또한 상태가 중심이 아니라 행동이 우선이 되야한다는 점을 알게되었고 db를 공부할 때가 생각이 난다. 결국 생각해보면 다 같은 맥락의 얘기인 것 같다. 최근에 입사하게 되었는데 주말에 잠깐 시간이 나서 오랜만에 객체지향 글을 쓴다. 코드를 보니 생각보다 보완해야될 부분이 많이보이는데 객체 지향 공부를 꾸준히 해서 더 좋은 코드로 개선하고 싶다.

코드

저번에는 코드 설명을 쓰지 않았는데 이때 나의 생각을 적는게 도움이 될 것 같다는 생각이 들어서 설명을 간단하게 적겠다. 상수라던가 타입은 생략했다.

 

나는 위의 라디오 버튼을 만들었다. 컨트롤러 파일을 하나 분리했는데 이 컨트롤러에서 앨리스의 height를 변경시킨다. 만약 위치를 변경시키는 버튼이 여러개였으면 이것도 하나의 파일로 분리했을 것이다. (같이 넣을려다가 최적화가 꼬여서 따로 분리했다.) 그리고 책에서 말한것처럼 상태를 기준으로 설계를 하지 않고 행동을 기준으로 설계를 했다. 사실 이건 너무 간단한 예제라서 어떤 방법으로 설계를 하던 상관없긴하다. 하지만 간단한것부터 기본기를 연습해야 더 큰 코드도 다룰수있기 때문에 의도적으로 그런 생각을 가지려고 한다.

 

index.tsx

const AliceInWonderland: React.FC = () => {
  const [print, setPrint] = useState<number | TLocation | null>(null);

  const [height, setHeight] = useState<number>(130);
  const [location, setLocation] = useState<TLocation>('문 밖');

  const onPrintHeight = () => {
    setPrint(height);
  };

  const onPrintLocation = useCallback(() => {
    setPrint(location);
  }, [location]);

  const onChangeHeight = useCallback(
    (changeHeight: number) => {
      setHeight((height) => {
        if (height + changeHeight < 20) {
          return 20;
        }
        return height + changeHeight;
      });
    },
    [setHeight],
  );

  const onGoOutside = () => {
    if (location === '문 안') {
      return alert('이미 문 안입니다.');
    }
    if (height < 40 || height > 80) {
      return alert('키가 40이상 80이하여야 문을 들어갈 수 있습니다.');
    }
    alert('문 안으로 들어갑니다');
    setLocation('문 안');
  };

  return (
    <Wrapper>
      <Print>{print}</Print>
      <FlexRow>
        <Button onClick={onPrintHeight}>키</Button>
        <Button onClick={onPrintLocation}>위치</Button>
      </FlexRow>
      <FlexRow>
        <AliceHeightController onChangeHeight={onChangeHeight} />
        <Button isRound onClick={onGoOutside}>
          문을 통과하다
        </Button>
      </FlexRow>
    </Wrapper>
  );
};

 

alice-height-controller.tsx

const AliceHeightController: React.FC<Props> = ({ onChangeHeight }) => {
  const [, setBeverage] = useState<number>(200); // ml
  const [, setCake] = useState<number>(1000); // g
  const [, setMushromm] = useState<number>(100); // g

  const onDrkinkBeverage = useCallback(() => {
    setBeverage((beverage) => {
      if (beverage < ONE_BEVERAGE) {
        alert('음료수가 없습니다');
        return beverage;
      }
      return beverage - ONE_BEVERAGE;
    });
    onChangeHeight(-100);
  }, [onChangeHeight]);

  const onEatCake = useCallback(() => {
    setCake((cake) => {
      if (cake < ONE_CAKE) {
        alert('케익이 없습니다');
        return cake;
      }
      return cake - ONE_CAKE;
    });
    onChangeHeight(+80);
  }, [onChangeHeight]);

  // 부채질
  const onFan = useCallback(() => {
    onChangeHeight(+70);
  }, [onChangeHeight]);

  const onEatMushroom = useCallback(() => {
    setMushromm((mushroom) => {
      if (mushroom < ONE_MUSHROOM) {
        alert('버섯이 없습니다');
        return mushroom;
      }
      onChangeHeight(-50);
      return mushroom - ONE_MUSHROOM;
    });
  }, [onChangeHeight]);

  return (
    <>
      <Button isRound onClick={onDrkinkBeverage}>
        음료를 마시다
      </Button>
      <Button isRound onClick={onEatCake}>
        케이크를 먹다
      </Button>
      <Button isRound onClick={onFan}>
        부채질하다
      </Button>
      <Button isRound onClick={onEatMushroom}>
        버섯을 먹다
      </Button>
    </>
  );
};

https://github.com/yoonminsang/object-programming-react/blob/main/src/components/alice-in-wonderland/index.tsx

728x90
LIST
댓글
공지사항