📜 제목으로 보기

  • 참고 유튜브 : https://www.youtube.com/watch?v=navJTjZlUGk
  • 정리본: https://github.com/LenKIM/object-book
  • 코드: https://github.com/eternity-oop/object
  • 책(목차) : https://wikibook.co.kr/object/

ch2. 객체지향이라면

value < identifier: 객체는 값이 아닌 식별자(메모리주소)로 식별

  • 값과 식별자

    • 객체지향에서의 객체 구분은 식별자(runtime 적재되어있는 메모리주소) 한다.

    • 값이 같다고 똑같은 객체X 메모리주소가 같다 = 같은 식별자 = 같은 객체

    • 값을 사용하는 context는 값만 같으면 같은 것이다.

    • 값이 같더라도 메모리로 같고 다름을 평가한다? 객체로 평가한 것이다.

    • java에서 문자열 비교시 .equals()를 사용하는 이유

      • java에서는 그냥 비교하면 식별자를 비교해버린다.
      • 문자열을 값으로서 비교를 원하면 equals를 써야한다.
    • 현대언어에서는 이 모순(그냥 비교 == 식별자 비교 like java)를 해결해서

      • 값 context를 쓰는 애들은, 그냥 비교 = 값 비교가 default가 연산자가 작동하게 된다.
      • javascript에서는 string을 객체로 안보고 값으로 본다.
        • 할당하거나 인자를 보내면 무조건 복제본이 된다.(주소전달X)
        • 비교시에도 안에 내용물만 비교
      • java에서는 string을 값으로 보기엔, 사람들이 너무 멍청하니까, 기본적으로 객체(식별자)로 기본 비교하게 해놨다. -> equals()를 써야 값으로만 비교한다.
        • 비교시, 메모리의 주소인 식별자로 평가된다.
    • name이라는 field를 이용해서, equals 메소드를 field 값== 같은지의 결과로 판단하도록 메소드를 짠다. image-20220122193121636

      • (필드)값으로써 비교하는 메소드가 된다.

      • 값으로 쓰려고 마음 먹은 메소드. 더이상 객체가 아니라고 생각한다

      • cf) 코틀린에서 == : 값으로 평가 equals()를 호출 / === : 식별자로 평가

        image-20220122193414611

        • 으로 평가하는 메소드 ValueType()는 true가 나오고
        • 식별자비교시에는 false가 나온다.
    • 내가 이 객체를 값으로 볼지 vs 객체로 볼지 를 먼저 정해야한다.

      • 객체로 본다면 -> 무슨 값을 가지든 식별자로서 확인이 된다
      • 값으로 본다면 -> 안에 있는 값만 확인한다.
  • 객체지향에서는 객체를 지향하므로 모든 평가를 식별자(identifier)로 평가한다?

    • Okay. 미리 정해진 답이 아니라면 객체로 쓰자.

      • 우리는 식별자로 시스템을 만들어가야한다

      • setter로 협력자 객체를 받아왔다?

        • 값으로 확인? (X) 식별자context를 통해서 식별한다.

        image-20220122194001367

    • 특정값을 정해서 미리 가지고 있는 유형이 아니라면

      • 객체지향이라고 해서 숫자, 문자 안쓰는 것은 아님.
      • 값 지향 -> 함수형 시스템에서는 참조를 지향하지 않는다
        • 불변성과 순수함수를 위해서
        • 똑같이 나오려면 값 지향을 해야한다.

Polymorphism: 이게 지원되야 진짜 객체지향

  • 객체지향이라 부를려면, 폴리모피즘(다형성)이 시스템에서 지원되어야 객체지향이라 부를 수 있다.
    • 이게 안되면 객체지향 방식으로 짜는 것일 뿐이다.
  • 다형성은 구체적인 2가지 요소가 충족되어야한다.
    • Substitution(대체가능성):
    • Internal identity(내적동질성):
대체가능성: 포인터1(위쪽 여러 형들, 껍데기)의 포인터2(변형가능성있는 메소드 or 변수)
  • 예를 들어 image-20220122195245713
    • 클래스 Worker가 Runnable(인터페이스)를 implements해서 run()을 구현하고 있다.
    • 이 때, 클래스Worker()객체는 Worker형이 아니라, Runnable형(인터페이스를 형)으로 지정해서 객체를 집어넣는다. image-20220122195422209
      • worker는 클래스Worker타입인데, 어떻게 인터페이스Runnable타입으로 들어갔을까
        • 클래스Worker가 인페Runnable을 구상한 것이기 때문에 가능해진다.
        • 자기형은 아닌데 자기형을 포괄하는 형에는 소속될 수 있다.(상속아니므로 실제 포괄X)
          • 클래스Wokrer는 자기형=Worker형뿐만 아니라 Runnable형을 가지고 있다. 둘 중에 하나를 선택해서 객체를 집어넣을 수 있다.
    • **포괄하는형에 넣은 순간부터는 worker는 Worker로서는 기능하지 못하고 Runnable로써 밖에 기능을 못한다 **
      • 왜냐면, Pointer of Pointer개념으로서, 껍데기인 Runnable의 포인터를 통해서 worker의 포인터를 다루게 되므로
      • Runnable의 포인터를 통해서 worker를 다루게 되므로
        • Runnable안에 있는 집합만 사용가능
        • Worker객체지만, Worker의 run()만 사용가능해짐
        • Runnable - run() —매핑—> 실제구현된 worker 속 run() 메소드
        • 사용자는 Runnable 밖에 못다룬다. -> run() 밖에 못다룬다.
        • run()을 찾아들어간다면? 직접 run()? ㄴㄴ
          • 관계에 의해 Runnable을 통해 -> run() -> 매핑 -> 실제구현된 worker의 run() 메소드까지 포인터의 포인터로 찾아들어간다.
      • 다형성을 쓰면, 직접 run()을 호출하는 것보다 느려질 수 밖에 없게 된다.
        • 포인터의 포인터를 찾아가서. 그냥 바로 쓰는게 아니다.
        • 다형성은 꽁짜가 아니라 비용이 크다.
  • 자세히 보자면
    • 인터페이스 상속한 클래스가 객체를 생성하는 순간, 쉽게 생각하려면, 객체 생성자 호출시 -> 메모리 속에 2개의 변수가 동시에 생성된다고 생각하자.
      • Runnable 객체
      • Worker 객체
      • 2개 중에 배정하는 형에 포인터를 배정한다.
        • Runnable
    • Runnable 객체인 worker.run()을 때리면 run()을 찾기 위해서
      • Runnable(껍데기 포인터)부터 찾아들어간 다음
      • run(내부 포인터)를 찾아들어간 다음
      • 실제 구현부 Worker - run()을 찾아들어간다.
  • 대체가능성(포인터의 포인터)을 실현하는 방법은 간단하다
    • 자바스크립트: 프로토타입 체인 생성 -> 포인터의 포인터…
    • 책에서는 동적 바인딩이라 부르는데, runtime에서 포인터의 연쇄를 계산하기 때문이다.
  • 이번에는 인터페이스 -> 구현이 아니라, class -> 상속받은 클래스를 예를 들어보자. image-20220122201811311
    • HardWorker()의 생성자를 호출하는 순간, 3개형의 변수가 메모리에 생성된다
      • Runnable
      • Worker
      • HardWorker
        • 3개 중에 배정하는 형에 포인터를 배정한다.
      • Runnable형인 worker = 변수에다가 할당해주는 순간
        • Runnable에 포인터를 배정하게 된다.
  • 대체가능성의 실체
    • 내가 객체를 만들 때, 다수의 형(상속, 구현 등 위쪽 껍데기-포인터1 후보들)을 선택할 수 있는 가능성이며
    • 하나의 형을 선택하면, 그 형에 해당되는 포인터를 노출함으로써 동적 바인딩으로 진짜 구현되어있는 곳으로 인도하는 행위
내적 동질성: 메소드참조는 다른 형인 상태라도 출신class메소드를 참조

image-20220122211317113

  • Worker클래스안에 print()라는 메소드를 신설했다.

    • 보면, 내부에서는 run()을 호출하면서 프린트한다.
  • HardWorker객체를 Runnable형이 아니라 Worker형으로 만들었다.

    • HardWorker()를 만드는 순간,
      • Runnable형
      • Worker형
      • HardWork형의 변수가 3개가 생성되며
    • Worker형에 넣은 순간, 포인터가 Worker를 가리킨다고 했다.
  • 그렇다면, worker.print() 때린다면

    • 포인터가 가리키는 —> Worker —> 그내부 print()호출시, println( run() )에서의 run()은 this.run()일 텐데
      • Worker내부의 run()일까? 아니면 HardWorker내부의 run()일까?
        • 나는 지금 Worker의 context로 들어와서 호출한 상태임.
        • context상 바로 위에 있는 Worker의 run()일까? 아니면 생성자 Hardworker()의 객체의 run()일까?
  • OOP 객체지향 프로그래밍에서 약속한 내적 동질성에 의해

    • 최초 생성한 객체(HardWorker)에 함수포인터는 여기로 유지하겠다.
    • 비록 형은 달라지더라도, 변형된 형(class)속 함수가 아니라 내적으로 동질성은 유지하기 위해 생성자를 호출한 출신지class의 함수를 참조한다.
      • Worker형으로 포장되더라도 run() 메소드는, Worker의 run()이 아니라 원래 객체의 class인 HardWorker의 run()을 참조한다.
      • 메소드 참조에서는 출신지가 더 중요하다.

object

  • 객체지향에서 객체가 갖춰야할 2가지
    1. 기능의 캡슐화(Encapsulation of Functionality)
      • ATM기: 외부에 상세기능을 다 노출하지 않고, 보다 잘 쓸 수 있게 노출한다.
      • 이것을 하는 이유는 궁극적인 목표는, 변화에 따라서 프로그램 수정/추가 등을 격리시키기 위함이다.
        • 캡슐화하여 일부만 외부노출 -> 다양한 나머지들은 내부에 갖힘 = 격리 -> 외부에 영향 줄어듬
    2. 상태 관리(Maintenance of State)
      • 관리 중 대표적인 것이 은닉화
      • 자율적인 데이터 갱신
      • 변화시 외부 노티피케이션
      • B가 배신때릴 때, C와 D가 망가진 것은 상태관리에 실패한 것
      • 객체지향이 레퍼런스를 가리키고, 식별자로 참작, 식별자로만 구분 등 하는 이유는 포인터의 포인터를 쓰기 위함이다. 더 근본적으로는 상태관리를 위해서
  • 격리되었는지 안되어있는지 확인 -> 해당안건의 특정 파일만 수정했는데 성공