OBJECT 02 식별자와 다형성(코드스핏츠)
object 책을 강의한 코드스핏츠 유튜브 요약
📜 제목으로 보기
- 참고 유튜브 : 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를 써야한다.
- java에서는
-
현대언어에서는 이 모순(그냥 비교 == 식별자 비교 like java)를 해결해서
- 값 context를 쓰는 애들은,
그냥 비교
=값 비교
가 default가 연산자가 작동하게 된다. - javascript에서는 string을 객체로 안보고 값으로 본다.
- 할당하거나 인자를 보내면 무조건 복제본이 된다.(주소전달X)
- 비교시에도 안에 내용물만 비교
- java에서는 string을 값으로 보기엔, 사람들이 너무 멍청하니까, 기본적으로 객체(식별자)로 기본 비교하게 해놨다. -> equals()를 써야 값으로만 비교한다.
- 비교시, 메모리의 주소인 식별자로 평가된다.
- 값 context를 쓰는 애들은,
-
name이라는
field
를 이용해서, equals 메소드를field 값
이== 같은지
의 결과로 판단하도록 메소드를 짠다.-
(필드)값으로써 비교하는 메소드가 된다.
-
값으로 쓰려고 마음 먹은 메소드. 더이상 객체가 아니라고 생각한다
-
cf) 코틀린에서
==
: 값으로 평가equals()
를 호출 /===
: 식별자로 평가-
값
으로 평가하는 메소드ValueType()
는 true가 나오고 -
식별자
비교시에는 false가 나온다.
-
-
-
내가 이 객체를 값으로 볼지 vs 객체로 볼지 를 먼저 정해야한다.
- 객체로 본다면 -> 무슨 값을 가지든 식별자로서 확인이 된다
- 값으로 본다면 -> 안에 있는 값만 확인한다.
-
-
객체지향에서는 객체를 지향하므로 모든 평가를
식별자(identifier)
로 평가한다?-
Okay. 미리 정해진 답이 아니라면 객체로 쓰자.
-
우리는 식별자로 시스템을 만들어가야한다
-
setter로 협력자 객체를 받아왔다?
- 값으로 확인? (X) 식별자context를 통해서 식별한다.
-
-
특정값을 정해서 미리 가지고 있는 유형이 아니라면
- 객체지향이라고 해서 숫자, 문자 안쓰는 것은 아님.
- 값 지향 -> 함수형 시스템에서는 참조를 지향하지 않는다
- 불변성과 순수함수를 위해서
- 똑같이 나오려면 값 지향을 해야한다.
-
Polymorphism: 이게 지원되야 진짜 객체지향
- 객체지향이라 부를려면,
폴리모피즘(다형성)
이 시스템에서 지원되어야 객체지향이라 부를 수 있다.- 이게 안되면 객체지향 방식으로 짜는 것일 뿐이다.
-
다형성은 구체적인 2가지 요소가 충족되어야한다.
Substitution(대체가능성)
:Internal identity(내적동질성)
:
대체가능성: 포인터1(위쪽 여러 형들, 껍데기)의 포인터2(변형가능성있는 메소드 or 변수)
- 예를 들어
- 클래스 Worker가 Runnable(인터페이스)를 implements해서 run()을 구현하고 있다.
- 이 때, 클래스Worker()객체는 Worker형이 아니라, Runnable형(인터페이스를 형)으로 지정해서 객체를 집어넣는다.
-
worker는 클래스Worker타입인데, 어떻게 인터페이스Runnable타입으로 들어갔을까
- 클래스Worker가 인페Runnable을 구상한 것이기 때문에 가능해진다.
-
자기형은 아닌데 자기형을 포괄하는 형에는 소속될 수 있다.
(상속아니므로 실제 포괄X)- 클래스Wokrer는 자기형=Worker형뿐만 아니라 Runnable형을 가지고 있다. 둘 중에 하나를 선택해서 객체를 집어넣을 수 있다.
-
worker는 클래스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()을 찾아들어간다.
- 인터페이스 상속한 클래스가 객체를 생성하는 순간, 쉽게 생각하려면,
객체 생성자 호출시 -> 메모리 속에 2개의 변수가 동시에 생성된다고 생각하자.
- 대체가능성(포인터의 포인터)을 실현하는 방법은 간단하다
- 자바스크립트: 프로토타입 체인 생성 -> 포인터의 포인터…
- 책에서는
동적 바인딩
이라 부르는데, runtime에서 포인터의 연쇄를 계산하기 때문이다.
- 이번에는 인터페이스 -> 구현이 아니라, class -> 상속받은 클래스를 예를 들어보자.
- HardWorker()의 생성자를 호출하는 순간, 3개형의 변수가 메모리에 생성된다
- Runnable
- Worker
- HardWorker
- 3개 중에 배정하는 형에 포인터를 배정한다.
-
Runnable형인
worker =
변수에다가 할당해주는 순간- Runnable에 포인터를 배정하게 된다.
- HardWorker()의 생성자를 호출하는 순간, 3개형의 변수가 메모리에 생성된다
-
대체가능성의 실체
- 내가 객체를 만들 때, 다수의 형(상속, 구현 등 위쪽 껍데기-포인터1 후보들)을 선택할 수 있는 가능성이며
-
하나의 형을 선택하면, 그 형에 해당되는 포인터를 노출함으로써
동적 바인딩
으로 진짜 구현되어있는 곳으로 인도하는 행위
내적 동질성: 메소드참조는 다른 형인 상태라도 출신class메소드를 참조
-
Worker클래스안에 print()라는 메소드를 신설했다.
- 보면, 내부에서는 run()을 호출하면서 프린트한다.
-
HardWorker객체를 Runnable형이 아니라 Worker형으로 만들었다.
- HardWorker()를 만드는 순간,
- Runnable형
- Worker형
- HardWork형의 변수가 3개가 생성되며
- Worker형에 넣은 순간, 포인터가 Worker를 가리킨다고 했다.
- HardWorker()를 만드는 순간,
-
그렇다면, worker.print() 때린다면
- 포인터가 가리키는 —> Worker —> 그내부 print()호출시, println( run() )에서의 run()은 this.run()일 텐데
-
Worker내부의 run()일까? 아니면 HardWorker내부의 run()일까?
- 나는 지금 Worker의 context로 들어와서 호출한 상태임.
- context상 바로 위에 있는 Worker의 run()일까? 아니면 생성자 Hardworker()의 객체의 run()일까?
-
Worker내부의 run()일까? 아니면 HardWorker내부의 run()일까?
- 포인터가 가리키는 —> Worker —> 그내부 print()호출시, println( run() )에서의 run()은 this.run()일 텐데
-
OOP 객체지향 프로그래밍에서 약속한
내적 동질성
에 의해- 최초 생성한 객체(HardWorker)에
함수포인터
는 여기로 유지하겠다. -
비록 형은 달라지더라도, 변형된 형(class)속 함수가 아니라 내적으로 동질성은 유지하기 위해 생성자를 호출한 출신지class의 함수를 참조
한다.- Worker형으로 포장되더라도 run() 메소드는, Worker의 run()이 아니라 원래 객체의 class인 HardWorker의 run()을 참조한다.
- 메소드 참조에서는 출신지가 더 중요하다.
- 최초 생성한 객체(HardWorker)에
object
- 객체지향에서 객체가 갖춰야할 2가지
-
기능의 캡슐화(Encapsulation of Functionality)
- ATM기: 외부에 상세기능을 다 노출하지 않고, 보다 잘 쓸 수 있게 노출한다.
- 이것을 하는 이유는 궁극적인 목표는,
변화에 따라서 프로그램 수정/추가 등을 격리시키기 위함
이다.캡슐화
하여 일부만 외부노출 -> 다양한 나머지들은내부에 갖힘 = 격리 -> 외부에 영향 줄어듬
-
상태 관리(Maintenance of State)
-
관리
중 대표적인 것이 은닉화 - 자율적인 데이터 갱신
- 변화시 외부 노티피케이션
- B가 배신때릴 때, C와 D가 망가진 것은 상태관리에 실패한 것
- 객체지향이 레퍼런스를 가리키고, 식별자로 참작, 식별자로만 구분 등 하는 이유는
포인터의 포인터
를 쓰기 위함이다. 더 근본적으로는 상태관리를 위해서
-
-
- 격리되었는지 안되어있는지 확인 -> 해당안건의 특정 파일만 수정했는데 성공