코드스피츠80_OOP design with game (1)- 1. 객체지향과 값지향

코드스피츠80_OOP design with game (1)- 1. 객체지향과 값지향

목차

코드스피츠 강의 정리록

생소한 도메인으로 배우는게 좋다.
익숙한 도메인들은 익숙한 처리방법으로 처리하기때문에 객체지향을 배우기 어렵다.
때문에 80기는 게임을 통해서 진행할 예정.


1. 객체지향 준비운동

객체지향 ←→ 값지향

객체지향의 반대말은 값지향이다.
객체지향과 값지향을 비교해보면서 기본개념을 잡고 넘어가자.


1.1 객체지향

1) 참조값을 사용함.

객체지향에서는
함수의 인자를 보내던,
변수에 대입을 하던,
연산을 하던
참조를 사용한다.


cf__1 언어마다 어떠한 타입에 대해서 값으로 처리, 참조로 처리할지 결정한다.

그렇다고 오브젝트는 값이 아닐 것이다 라고 생각하면 안된돠.
오브젝트도 값으로 생각하는 언어들이 있다.

  • 자바스크립트에서는 스트링을 값으로 보고 있다.
  • 자바에서는 스트링을 객체로 보고 있다.

언어마다 어떠한 타입에 대해서 직접적으로 값으로 처리할지
참조로 처리할지 결정하는 것이다. 원래 값이고 원래 참조다 라는 개념은 없다.


* 한번 만든 객체가 전파됨.

참조는 순식간에 전파된다.
객체 컨텍스트에서는 참조를 쓴다고 했다.
참조가 되면 원본은 힙메모리에 만들어지고
모든 객체는 힙메모리의 주소만 참조하고 다니는 것이다. 주소값만 복사되고 있는 것.
⇒ 한번 만들어진 객체는 통제권을 쉽게 벗어나서 주소의 복사가 왕창 일어난다는 것이다.

객체지향의 큰 어려움은 객체를 일단 만들고 나면
주소값을 보호할 수 있을 것 같지만 실제로는 순식간에 퍼져나간다.
⇒ 문제는 그 주소값을 참조하는 변수에 새로운 객체를 할당할 수 있다.

A라는 변수가 원래는 old 객체를 참조하고 있었는데,
A라는 변수에 new객체를 할당하면 새로운 주소가 A변수에 들어간다.
⇒ A는 아무 문제가 없지만, B = A, C = B의 B,C변수들이 다 바보가 된다.
즉, A의 참조부분을 잡았던 애들이 다 바보가 된다.
B와 C는 old를 가리키고 있지만, A는 이제 new가 되었다..

객체지향에서는 객체 참조키를 함부로 노출하지 않고 쥐고 있지 않으면
순식간에 오염이 전파되서 A객체도 new객체를 받아들일 수 없다. 겁나니까.

객체지향은 이러한 단점이 있다. 참조가 쉽게 전파된다.
참조 전파를 막는 장치가 있거나,
섬세하게 관리하지않으면 참조가 순식간에 전파되어서
참조를 전파시킨 본인조차도 새 객체를 받아들일 수 없다. 그럼.. 전체가 썪어서 망가진다..

*이 부분을 관리하는 것이 객체지향에서 어려운 점이다. *

그에 비해서 값지향은 매번 복사가된다. 전파되지 않는다는 것이다.
끊임없는 클론이 양산된다.
⇒ 메모리가 엄청나게 많이 사용된다.
⇒ GC에 의존할 수밖에 없다.

값으로 계속 복사가 되기때문에
값에 대한 컨텍스트를 이어갈 수 없고
컨텍스트를 포함한 새로운 값을 만드는데 성공해야한다.

값지향에서는 히스토리를 볼 수 없고 상태의 흐름을 볼 수 없다.
중간중간에 흔적을 따로따로 상태를 보관해줘야한다.

결과물이 복잡할수록 함수의 복잡성은 올라간다.
⇒ 수정은 더욱더 올라간다.
ex) 함수형, rx

객체지향은 싱글톤 객체나 메모리상의 유일한 객체를 참조로 전파시키고 있으니까,
메모리상의 유리한 점이나 객체의 생성에 대한 안정성을 확보해 줄 수 있다.
다만, 주소의 전파가 일어나면 썩어들어간다.


* 데이터를 처리하는 메소드를 내장함.

본인의 상태를 본인이 책임질 수 있다.
class 내에 메소드들이 this가 안나오면 메소드가 아니다.

  • this가 없고, 인지와 지역변수만 쓰는 것은 유틸리티 함수이다.
  • 우리는 기계적으로 class코드를 살펴보고 메소드에 this가 나오는지 안나오는지 살펴보고 안나오면 전부 유틸리티로 보내야한다.
  • 다른 함수에서 유틸리티를 사용할지도 모르고,
    나중에 유틸리티 함수로 분리하지 않고, 리팩토링이나 수정시
    비슷한 함수를 사용하는 위치가 어디있는지 모르게 되며, 중복코드가 생길 가능성이 있다.

* 모든 메소드는 현재 객체 컨텍스트 사용

리팩토링할 때 알 수 있는 것은
우리가 짠 클래스에 this를 사용하지 않는 메소드가 나온다는 점으로부터,

  • 이건 객체협력으로 문제를 풀고 잇지 않구나를 알 수 있다.
    값 컨텍스트를 섞어서 문제를 풀었다는 것을 알 수 있다.

리팩토링이 끝나고 나면, this만 사용하는 클래스의 협력으로 문제가 풀리게 된다.
그것이 우리의 궁극적인 목표이기도 하다.


2) 각 객체는 단일한 책임을 갖음 SOLID (마틴 파울러)

우리가 알파고가 아니다. 이세돌이다.
사람의 한계로 인해서 한번에 하나밖에 안된다.
인간의 한계때문에 SOLID원칙이 있는 것이다.

⇒ 그래서 컨벤션이 존재.

  • 자신이 처리할 수 없는 책임은 타 객체와 협력
  • 객체망에 참여하여 자신의 역할 수행g하도록 한다.

3) 객체지향 사고

  1. 객체망으로 문제를 해결한다.
    • 혼자할 수 있는 일이 단일책임밖에 없기 때문에
      객체망으로 해결한다.
  2. 단일책임을 준수한다.
  3. 객체를 이용하여 문제를 해결한다.
    • 값으로 문제를 해결하지 않는다.
    • 모든 처리를 객체로한다.
    • 값을 도입하면 도입할수록 객체지향이 아니게된다.
  4. 은닉캡슐화를 활용하여 상태를 처리한다.
    순식간에 전파되기 때문에.

은닉

왜 안보여 줄까? ⇒ 지식 (knowledge)
은닉을 하는 이유는 사람때문이다. 사람을 못믿기 때문에..
객체지향은 사람 중심의 사고로 사람을 무지하다고 생각하는..ㅋㅋ 패러다임이다.

캡슐화

은닉과 전혀 다른 이야기이다. 나의 행동을 구체적으로 부여주지 않으려고 하는건 아니다.
추상화된 수준으로 설명하고 싶은 것.
(ex_ATM기기: 돈세는 소리로 사용자의 목적을 인지시키지만, 실제 내부에서는 많은 일이 벌어진다.)

캡슐화를 common sense로 제공한다.
*가장 설계능력이 많이 필요한 부부은 캡슐화이다. *
좋은 아키텍쳐는 캡슐화를 잘한다.
캡슐화를 하는 이유 중 하나는 객체망을 숨기기 위한 것도 있다.
아키텍쳐들이 하는 일들이다.

기계 친화적인 코드는 없다. 기계친화적인 포맷은 없다.
사람에게 이해가 쉬운 코드. 사람이 잘 사용할 수 있는 캡슐화.

⇒ 객체지향이 지원하는 것이다.


1.2 값지향

1) 값의 복사를 사용함.

* 값의 평가 방법

값이라는 것은 어떻게 구분할까?
객체 간의 식별은 메모리의 주소로 한다.

값에서는 메모리의 주소는 틀리지만,
타입에서 인정한 동과평과방법에 의해서
값의 동과를 판별하는 전용식이 따로 있다.

메모리의 주소로 평가하는 것이 아니라
값에 따라서 평가하는 방법이 다 다르다.


* 값을 처리하는 유틸리티 함수가 존재

값에서는 값을 평가하는 기준이 따로 있고,
그것으로 동과를 평가한다. (메모리의 주소로 평가하지 않는다.)
값이 있으면 값의 동과를 평가하는 식은 값 밖에 있다.
메소드도 아니다.
메소드로 처리하지 않고 값을 처리하는 유틸리티 함수로 값을 처리하게 된다.

  • 타입 그룹별로 존재하게 된다.

* 함수가 값을 처리하는 경우 컨텍스트는 없음

메모리에 있는 값 그 자체를 사용한다.

cf__2. 객체도 값처럼 사용하면 값 컨텍스트로 넘어간다.
자바같은 언어에서 객체는 생성할때 무조건 고유한 해시코드를 갖고 태어난다.
객체는 원래 메모리 주소로 구분해야하는데, 해시코드를 어딘가에 캐시잡아놓고, 해시코드와 해시코드를 비교해서 객체의 동과를 비교하려고 한다. 이런 경우 객체지만 값 컨택스트로 넘어간다.
메모리의 주소로 비교해야하는데, 특별한 값의 비교방법으로 동과를 평가하면 그 비교식들은 순식간의 값 컨텍스트로 바뀐다.


2) 값은 순수한 데이터로 책임의 개념이 없음

* 각 역할에 맞는 적절한 값의 타입이 존재함

값 지향에서는 값과 독립적인 함수일가?
인티저를 더하는 함수는 값에 독립적일까? 독립적이다.
단지 타입에 종속되어있다.

어떤 함수는 인티저 타입만 건드릴 수 있고,
어떤 함수는 더블 타입만 건드릴 수 있다.
이때 말한 타입은 객체타입이 아님.

타입에 따라서 값타입이냐
객체타입이냐는 굉장히 큰 구분점을 갖게 된다.
값타입에서는 그 타입이 함수를 그룹짓는 기준이 되어버린다.

값에서는 하나하나 값을 구분하는 것이 아니라
값의 타입만 구분할 수 있기 때문이다.
그 다음에는 이 값과 값의 크기만 갖는것이다.

객체는 그렇게 구분되지 않는다.
메모리만 있으면 독립성이 확보이다.
값은 복잡한 녀석들이다 의외로


* 값을 처리하는 함수의 조합으로 문제를 해결함

값은 계속 복사되고, 계속 역할이 없는 상태로 존재하기 때문에
값지향을 이용해서 문제를 처리하기 위해서는
값을 처리하는 함수의 조합으로 문제를 처리하는 수밖에 없다.

HOC

끊임없이 그 함수가 값을 만들거나 값을 받아들이면서
최종적으로 도메인을 해결하기 때문에
결국에 값을 통한 문제들은 대부분 파이프라인으로 결론을 내게된다.

ex__도자기 공정
도자기를 예로 들자.
도자기를 만드는 방법을
흙을 반죽하고 ⇒ 반죽한 흙을 빚어서 도자기를 만드는 과정이라고 치면.
과정을 유틸리티 함수로 만들어보자.

함수는 흙을 받고, 반죽을 return하게끔 만들 수 있다.
이런 함수는 고정상태와 고정출력이 있다.
어떤 과정을 순수하게 고정상태와 고정출력이 있는 상태로 구분지어서 나눌 수 있냐가 중요하다.

- 값지향을 하기 위해서는 상태를 구분지을 수 있냐 없냐 즉,
내가 처리하려는 공정을 딱딱 잘라낼 수 있는지 없는지가 굉장히 중요하다.

하지만 우리 비즈니스의 요건들은 딱딱 잘라낼 수 있는 상황이 아니다.
무조건 아닌 것은 아니고, 우리가 속해있는 도메인에 달려있다.
값지향으로는 가역성을 만들기 어려워서 리셋을 한다.


3) 값 지향 사고

  1. 연산을 통해 문제를 해결한다.
  2. 연산에 적합한 값을 정의한다(함수포함)
  3. 값을 통해 문제를 해결한다.
  4. 순수함수를 활용하여 상태를 제거한다.

값과
immutable과
rx와
함수형으로 풀 수 있는 문제는
그렇게 많지 않다.

주요 도메인은 객체지향을 쓰는 이유는 사람의 마음은 기계가 아니기 때문에..
개발 잘 하고싶으면 인지과학, 인지심리, 뇌과학 배우면 좋다.
사람들이 좋아하는 것을 하면 좋은 아키텍처가 될수있다 .. :)


1.3 객체지향에서 단일 책임의 도출 기준

객체 디자인 또는 설계

객체의 책임을 도출하고 객체망을 구성하는 것

책임 (or 역할)을 도출하는 기준으로 사용할 수 있는 후보군
3가지 요령이 있다. (3번으로 갈수록 뒤로갈수록 중요성이 높다.)


1) 도메인 - 의존성

객체디자인을 할 때 도메인에서 뽑는 것이 좋다.

  • 의존성 관리가 훨씬 쉬워진다.
  • 객체망을 구성할때 독립적인 단위로 구성할 수 있다.
* 첫번째로 역할을 인식할때는 도메인 경계를 나누고 시작하자.

DDD 개발: 의존성때문에!

  • 의존성의 범위를 정의할 수 있기 때문에
  • 가역성이 좋아진다.

2) 네이티브 영역 - 가변성

개발은 크게 2가지 영역을 다룬다고 생각해도된다.

  1. 인메모리 객체
    로직이 메모리안에서 도는 로직들이 있다.
    -
  2. I/O (네이티브 영역)
    통제권 밖에 잇는 것.
    ex. DOM, 쿠키, ajax
* 인메모리와 인메모리 바깥 생태계가 주고받는 행위가 존재한다.
  • 쿠키를 읽어온다. : 하드에서 읽어옴
  • 네트워크 통신 : 소켓으로 읽어옴
  • 돔하고 대화..

I/O를 사용하면 드득거린다.
쿠키를 읽을때 드득거리는 것을 볼 수 있다.
때문에 일부러 웹워커같은 것을 사용한다.

  • ajax 비동기인데 왜 워커에서 띄어서 하지?
    • ajax가 I/O라서 ajax가 발동하는 순간을 보면 드득거린다.
    • 백그라운드 thread를 띄어서(웹워커) 여기서 I/O를 띄어야,
      메인 thread에 영향을 안주게 된다.
* 네이티브 영역: I/O영역

인메모리 영역과 네이티브 영역을 분리해서 객체를 구성해라.

믹스한 경우

  • ex__
    인메모리 객체와 돔을 믹스해서 사이트를 만들엇을 경우
    돔말고 캔버스로 바꾸면? => 고우투 헬

이 두개를 분리한 사례

  • React
  • 리액트의 인메모리 구조체가
    네이티브와 완전히 분리되어있기 때문에
    . 리액트로 짠 코드를
    리액트 네이티브로 바꿀 수 있고
    리액트 캔버스로 바꿀 수 있다.
    . 리액트 본체가 완전히 인메모리로 되어있기 때문에 가능한 상황.

도메인보다 더 중요하다.
네이티브 도메인을 분리해주자.


정리 **

네이티브 영역을 분리해서 프로그램을 구축하지 않으면
새로운 네이티브 영역과 바인딩하거나 환경이 바뀔때 비용을 든다..
네이티브 영역에 의존하면 안된다.
의존하지 않기 위해서는 인메모리, I/O 따로 만들어야한다.

도메인 패턴
도메인 패턴을 따르지 않는 패턴을
enterprise pattern에서 transaction script pattern


3) 변화율 - 유지보수

변화율의 기준으로 나누어야한다.

예를 들어
고객의 비즈니스 요구사항이 바뀔때 바뀌는 부분이고
고객의 시장반응이 바뀔때 바뀌는 부분이라면
해당 변화율 기준으로 나눠놔야한다.

구간을 나눠서, 책임을 나눌 때
어떠한 일때문에 바뀌는지에 따라서 나눠 놓으라는 것.

모든 프로그램은 변하니까.
변하는 이유에 따라서 나눠놔라.
좋은 아키텍트가 되는 중요한 자질이다.

변화율을 꽤뚫어봐라! 변화율에 대한 센스가 아키텍트의 중요한 자질.



도메인을 기준으로 책임을 도출해보자.
네이티브 영역을 분리해서 책임을 도출해보자.
변화율을 인식해서 책임을 도출해보자.

📚