Polymor!

객체지향적으로 설계하다(feat.포인터) 본문

Architect & Design

객체지향적으로 설계하다(feat.포인터)

Megan Kim 2020. 12. 10. 12:04

프로그램을 제작하거나 서버를 구축하는 일을 하다보면.. 프레임워크, 빌드툴, 클라우딩 컴퓨팅, CI/CD... 등 정말 알아야할 기술 스택들이 화려하다. 수 많은 팀프로젝트의 팀원으로 작업을 해보았지만, 생각보다 우리의 주 관심사가 "구현"에 중점을 두는 일이 많이 없어진다. 

' 구현은 어떻게든 하면 되는거고....' 

 

물론 다양한 기술 스펙의 경험도 좋다지만, 내가 항상 고민하고 또 고민하는 것은 '어떻게 객체지향스럽게 설계하고 구현할 것인가? '이다. 

오늘은 그 주제로 이야기를 해보고자한다. 아래 도서는 존경하는 한 개발자 동료분의 추천으로 사게된 도서인데.. 주니어 개발자에겐 다소 어려움이 있을 수 있으나 큰 그림을 그리고 설계할 수 있는 힘을 길러줄 수 있는 내용들이 많기 때문에 나로선 적극 추천한다 :>) 

 

 

 

 

 

 

 

* 우선 객체를 이해하기위해 '포인터'개념이있고 메모리영역을 직접 관리하는 언어로 대표주자인 C언어로 살펴보자.

C를 쓰다보면 동적으로 할당한 메모리를 직접 해제해주는 작업은 개발자의 몫이다. 

free(void *ptr);

이외에 Python, Java 등 최근 나온 언어들은, 개발자가 신경을 쓰지않아도 알아서 내부에서 힙영역 메모리를 관리해준다.

자바의 '가비지 컬렉션' 이 그 대표적인 메모리 관리자이고, 파이썬도 마찬가지 비슷하게 Reference Counting 개념으로 관리가 된다. 

이론적으로 메모리 누수를 막기위해~ 라고 학교에서 배우지만, 사실 얼핏듣기엔 굉장히 추상적이다. 

 

memory area

이런 도표를 많이 봐왔다. 그러나 잘 와닿지않을 수 있다. 마음으로 이해하려면 직접 코드를 짜서 확인을 해보는 방법이 최고다. 

C로 예를 들어 이 개념을 조금 더 구체화 시켜보겠다. 아래와 같이 코드를 작성했다고 가정해보자.

 

 

// test.c 

#include<stdio.h>

    int a=1;

int main(){

    int c;

    int *d = &c;

    int *e = (int *)malloc(sizeof(int));

  

    printf("%p %p %p %p %p\n",&a,&b,&c,&d,&e);      // 변수 a~e의 주솟값을 출력

    printf("%p %p \n",d,e);         // 변수 d 와 e 의 value값 출력  (포인터이니 주소값을 가지고 있을거다.)

    free(e);

    return 0;

}

 

test.c 파일을 컴파일하면, test.o라는 목적파일이 생성된다. 그리고 이를 실행하면 운영체제가 RAM 어딘가에 사용할 영역을 할당해준다. 

이때의 결과값을 들여다보면, 대충 서로 다른 영역 혹은 순서대로 같은 영역에 할당되는게 구분이된다. 

 

0x100e4c024 0x7ffeeedb49c8 0x7ffeeedb49c0 0x7ffeeedb49b8

0x7ffeeedb49c8 0x7fc036c00300 

 

각각의 주소값이 Data (전역변수) / Stack (지역변수) / Heap (동적 메모리 할당 변수) 영역임이 눈에 보인다. 참고로 포인터는 8바이트라 8칸씩 차이나고, 스택은 높은 주소에서 낮은 주소로 할당되는게 확인된다. 반대로 힙은 낮->높인데 , 스택과 힙 총합 메모리가 고정되기때문이다. 왜일까? 이 영역은 런타임에 결정이 되는데 , 예를 들어 개발자의 실수로 무한한 재귀함수호출로 스택이 오버플로우 나게되어 운영체제가 허락하지 않은 곳에 메모리가 Write 되면 큰 문제가 될 수 있으니, 스택은 반대로 자라 이를 방지하게 되는것이다. 

 

쉽게 말해, 포인터가 곧 객체이다. 변수는 주소만 가질뿐.. 실 데이터는 힙영역에 객체로 자리잡게 되고 변수는 그를 가리킨다.(=reference)

 

Java , Python 등 메모리를 알아서 관리해주는 언어에 익숙한 사람은 위의 내용들은 다소 생소할 수 있다.

심지어 Python은 모든게 객체(OBJECT) 로 만들어진다. 

a = 10   # a = new Int(10) 같이 10이라는 정수형 객체가 생성된다.

"hello".replace('h','H')  # 문자 또한 마찬가지이다

 

python에서는 mutable(list,dictionary,set) , immutable(string,tuple,int,float,boolean) 등으로 가변,불변 변수로 구분을 한다.

### List 는 mutable 이다 ###

old = [1,2,3,4]

new = old

new.append(5)

print(old) # [1,2,3,4,5]
print(new) # [1,2,3,4,5]

 

### 따라서 원본을 Copy해주는 것에 신경을 써야한다. ###

old = [1,2,3,4]

new = old[:] # 혹은 new = old.copy()

new.append(5)

print(old) # [1,2,3,4]
print(new) # [1,2,3,4,5]

 

 

 

* 그래서 객체지향스러운게 뭔데?

객체는 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원이다. 클래스를 고민하기 전에 어떤 객체들이 필요할지 생각하여야한다. 클래스는 공통의 상테와 행동을 공유하는 객체들을 추상화할 뿐이다. 

객체는 상태와 행동을 가지는 복합적이고 자율적인 존재이며 역할과 책임 그리고 협력하는 존재이다. 

구현의 편리함을 위해 캡슐화, 상속과 인터페이스, 다형성 등의 개념이 확장성을 주는 것이지, 언제나 본질은 객체에 있다. 

 

클래스가 있고 인스턴스화 된 객체가 아니라, 객체가 먼저이고 클래스는 껍질이다. 객체는 동적이며 각자의 역할이있다. 

가장 중요한 객체지향의 설계점은 객체들간의 결합도를 낮추고 자율성과 응집도를 높여야하는 것이다. 이런 일련의 설계들은 결국 Trade-Off로 균형을 이루어야하는 것이 최종적인 목표이다. 그래서 어떤 사람들은 객체는 살아있고 객체설계는 하나의 예술적 행위라고 이야기한다. 

 

 

 

Comments