현대의 수많은 프로그래밍 언어에서 객체 지향 프로그래밍을 지원한다. 객체 지향은 현대에서 가장 많이 쓰이는 프로그래밍 패러다임이며, 프로그래밍을 공부한다면 필연적으로 마주치게 되는 것이 객체 지향 프로그래밍이다. 웹 프론트엔드, 웹 백엔드, 게임 개발, 모바일 개발 등 수많은 분야에서 객체 지향 프로그래밍에 의한 개발이 이루어진다.
객체 지향 프로그래밍이 무엇인가 설명하고자 한다면 말문이 막힌다. 객체 지향을 바라보는 관점에 따라 객체 지향을 설명하는 방식이 달라진다. 객체 지향 프로그래밍에 대한 명료한 개념을 형성하지 않은 채 객체 지향 패러다임을 적용해 개발하는 개발자도 많다. 나 또한 객체 지향을 설명하라는 요구를 받으면 첫 마디를 떼는 데 상당한 시간이 걸린다. 나는 이 지면에 내가 나름대로 객체 지향을 이해한 바를 서술하고자 한다.
프로그래밍이란 것은 무엇인가? 프로그래밍은 현실세계의 문제를 해결하기 위한 소프트웨어를 개발하는 과정을 의미한다. 객체 지향 프로그래밍, 또는 객체 지향 패러다임은 그러한 소프트웨어를 개발하기 위한 프로그래밍 방법론의 일종이다. 객체 지향 프로그래밍은 이름 그대로, 객체를 지향하는 프로그래밍 방법론이다. 객체 지향 프로그래밍은 하나의 방법론이고, 우리가 주목해야 할 것은 객체 그 자체이다. 객체를 설명하기 전에, 우선 추상화가 무엇인지부터 알아야 한다.
세계는 인간이 온전히 파악하기에 너무 복잡하다. 프로그래밍을 한다고 해 보자. int a = 13 + 15
라는 단순한 구문도, 프로그램 카운터(Program Count, PC)에서 메모리 주소를 가져와 메모리에 저장된 instruction에 접근하고, instruction을 디코딩하고, 레지스터에 값을 할당하고, 레지스터에 할당된 값을 산술 논리 장치(Arithmetic Logic Unit, ALU)에 보내 연산을 시행하고, 그 결과를 메모리와 레지스터에 저장하는 등 일련의 복잡한 과정을 거친다(간단한 프로세서 아키텍처를 가정한다). 프로그래머가 이 모든 과정을 일일이 정의한다고 하면, 개발 생산성이 얼마나 떨어질까?
사람의 인지 능력에는 분명한 한계가 있고, 이 세계를 온전히 파악하고 이해하는 것은 사람의 능력을 아득히 초월하는 일이다. 그래서 사람은 추상화를 거쳐 이 세계를 이해하는 전략을 채택했다. 추상화는 어떤 종류의 대상들에 대해 그것이 가져야 할 핵심적인 특징들을 가지는 모델을 만드는 것이다. 프로그래밍 언어라는 모델은 상술된 복잡한 일련의 과정을 단순히 int a = 13 + 15
라는 한 줄의 구문으로 추상화했다.
타입은 컴퓨터 세계에 적용된 추상화의 한 방법이다. 10이라는 숫자를 표현하고자 한다고 가정해 보자. 컴퓨터가 데이터를 저장할 때 이진법을 사용한다. 십진법 체계를 사용하는 우리가 10이라는 숫자를 컴퓨터에 표현할 때, 이 숫자는 1010
이라는 이진수로 저장된다. 그런데 정수만이 이런 방식으로 저장되는 것이 아니다. 실수, 문자 또한 이진수로 저장된다. 그뿐만 아니라 그림, 동영상 또한 이진수로 저장된다. 101010111010101101011
이라는 이진수가 있다고 해 보자. 이것은 정수인가? 실수인가? 아니면 배열이나 문자열인가? 컴퓨터 입장에서 어떤 것이든 상관없다. 실제로 어셈블리어를 들여다 보면 그곳에 타입이 명시된 바는 없다(x86 아키텍처와 같이 피연산자의 데이터 크기가 연산자에 suffix 등으로 표시된 경우는 있다). 그러나 프로그래머 입장에서 이것이 정수인지 실수인지 배열인지 문자열인지 알아야 이 데이터를 다룰 수 있다.
그래서 도입된 것이 데이터 타입(Data Type)이다. 데이터 타입은 데이터에 맥락과 크기를 한정해 준다. 데이터 타입에 의해 해당 데이터가 정수인지 실수인지 배열인지 문자열인지 파악하고 어떤 연산을 적용시킬 수 있는지 결정할 수 있는 것이다.
int num = 10;
num
변수는 int
형이다. 이 코드가 컴파일돼서 바이너리 코드가 됐을 때, 타입 정보는 소거된다. 오로지 num
변수에 1010
이라는 이진수가 저장될 뿐이다(바이너리 코드에서 변수 정보 또한 소거된다. 데이터는 레지스터에 저장될 수도 있고 메모리에 저장될 수도 있다). 그러나 타입에 의해 프로그래머는 num
이 정수형 변수란 것도 알 수 있고 여기에 10이라는 정수가 저장될 거란 것도 알 수 있다.
정수가 가지는 핵심적인 특징을 도출하고 정수 타입이라는 모델을 만든 추상화의 원리 덕분에, 우리는 저 데이터가 실제로 저장되는 방식을 몰라도 저 데이터가 무엇을 나타내는지를 알고 데이터를 다룰 수 있다. 프로그래머는 데이터의 구현(Implementation)이 아닌 표현(Representation)에 집중할 수 있게 된다.
객체는 추상화의 원리를 적용해 현실세계의 사물 및 현상을 소프트웨어 세계에 표현한 대상이다. 상점 시스템을 표현한 다음 예제를 생각해 보자.
public class Commodity {
private String name;
private int price;
private CommodityType commodityType;
// Constructor, getter, setter
}
상점에서 파는 상품을 클래스로 표현했다. Commodity
객체는 이름(String name
)과 가격(int price
), 그리고 상품 종류(CommodityType commodityType
)을 갖는다. 그러면 상품을 구매하는 행위를 객체화해 보자.
public class Purchase {
private Commodity commodityToBuy;
private int quantity;
// ...
}
Purchase
객체는 Commodity
객체를 내부에서 참조(Reference)한다.
실제 상품이 컴퓨터 안에 존재하는가? 실제로 상품을 구매하는 행위가 컴퓨터 프로그램 안에서 진행되는가? 그렇지 않다. 실제 상품은 현실세계에 존재하고 거래 또한 현실세계의 계산대에서 이루어진다. 그러나 우리는 Commodity
객체와 Purchase
객체를 각각 상품과 구매 행위로 인식하고 프로그래밍을 진행한다. 실제 세계의 상품과 구매 행위의 핵심 특징을 도출하여 Commodity
객체와 Purchase
객체로 추상화한 것이다.
Commodity
객체와 Purchase
객체의 인스턴스는 메모리에 1과 0으로 저장될 것이다. 프로그래머가 직접 메모리를 현미경으로 들여다보며 1과 0의 배열을 보면 Commodity
객체와 Purchase
객체를 파악할 수 있을 것인가? 컴파일된 (어셈블리된) 기계어를 직접 읽어서 어떤 것이 Commodity
객체고 어떤 것이 Purchase
객체인지 알 수 있는가? 그렇지 않다. 우리는 1과 0으로 이루어진 구현체에 Commodity
와 Purchase
라는 타입을 부여함으로써 그것들을 객체로 인식할 수 있는 것이다. 이것이 객체 지향 프로그래밍에서 추상화가 적용된 원리다.