이번 시간은 '객체 지향 프로그래밍(Object Oriented Programming, OOP)'에 대해서 알아보려 한다. OOP는 문자 그대로, 프로그래밍을 할 때 실행되는 모든 로직을 '상태'(properties)와 '행위'(method)로 이루어진 객체(Object)로 만드는 것을 지향하는 일종의 프로그래밍 패러다임이다. 이는 어디선가 제공되는 라이브러리 또는 익스텐션은 아니며, 일종의 방법론 또는 사상이라고 할 수 있는 철학적 개념이다.

하지만 오히려 이러한 설명들이 OOP라는 개념을 접하는데 굉장히 거부감을 주게 된다고 여겨진다. 먼저 '객체'라는 단어를 잘 사용하지 않으며, 애초부터 추상적 개념의 고급 어휘이기 때문이다.
그래서 비슷한 개념의 추상적 단어지만 보다 자주 사용되는 말로 표현한다면, 보다 접근하기 쉬워보인다. 이를테면 '어떤 물건', '어떤', '무언가' 등 말이다.
하지만 '무언가 지향 프로그래밍'이라고 할 수는 없지 않은가

객체지향프로그래밍(OOP)의 개념은 어디서부터 오게 되었는가? 라는 질문을 하게 된다면, 단연 사람은 보다 편리하고 좋은 것을 얻기 위해 노력한다. OOP 또한 이러한 부산물이라고 볼 수 있는데, 컴퓨터 프로그래밍의 지난 발자취를 살펴보면 단방 이해할 수 있다.

컴퓨터 프로그래밍

컴퓨터 프로그래밍이란, '프로그램 언어'를 통해 소프트웨어를 개발하는 행위를 뜻한다. 그런데 현대에서는 프로그램 언어를 '기계어', '어셈블리언어', '고급 언어' 세 가지로 나누어 본다.

1) 기계어(machine language)

기계어는 컴퓨터 등 전자 기기가 직접 읽을 수 있는 2진 숫자(binary digit, 0과 1)로 이루어진 언어를 말한다.

영화 <매트릭트>에서 네오가 각성한 후 매트릭스 세계(기계 세계)의 본질을 꿰뚫어 볼 때 나타난 0101100101101101100111101...을 말한다.

2) 어셈블리언어(프로그래밍 저급 언어, assembly language)

어셈블리언어 또는 프로그래밍 저급 언어는 컴퓨터 상업화가 이뤄질 때 많은 사람들이 사용할 수 있도록 위와 같은 기계어를 사람이 읽을 수 있는 자연어에 가깝게(위 사진 왼쪽 모습) 10여개의 문자로 '기호화'해서 만든 언어이다.

지금 보면 너무 극혐에 가까운 언어 상태이지만(C, C# 등이 이와 비슷하다고 한다), 그때 당시만 해도 오른쪽 상태(더극혐)를 왼쪽으로 바꿀 수 있는 것으로만 해도 센세이션이라고 볼 수 있다.

3) 고급 언어(high level language)

고급 언어는 인간이 이해하기 쉬운 프로그래밍 언어로써 영어(English, also 극혐) 문법과 굉장히 흡사하게 만들어졌다. 그래도 배우기만 한다면 사람답게 말할 수 있는 수준으로 올라왔으니 대단하다고 볼 수 있다.

그런데 이런 언어들의 발전이 객체지향프로그래밍과 무슨 상관이냐고?

그 이유는 당시 고급 언어가 엄청난 센세이션을 불러오며 수많은 프로그래밍이 남발하게 되면서 '절차지향프로그래밍''객체지향프로그래밍'으로 나뉘게 됐다.

두 개념을 원시시대에 비교하자면,
전자는 한 개인이 살아가기 위해서 모든 것을 만들고 수집하고 해야하지만,
후자는 부락과 촌을 구성해 함께 '공유', 즉 '재사용'할 수 있다는 혁명을 이뤄낸 것이다.

코드를 통해 표현해보자면 아래와 같다.
'절차지향프로그래밍'은 한 프로그램 안에서 변수 또는 함수를 생성하기 위해서 필요한 모든 것들을 순서(절차)에 따라 만들게 된다.

let tool = 10
let power = 2
let height = 180
function effi(tool, power, height) {
    return power * tool * height;
}

하지만 무언가 만드는 효율은 각자 다르다. 집을 만드는 효율, 불을 지피는 효율, 사냥을 하는 효율 등 말이다. 그리고 각각의 효율을 구하기 위해서는 새로운 변수(properties)와 함수(function)을 만들어야 한다. 한 두개로 끝난다면 다행이지만, 현대 소프트웨어에서는 셀 수 없이 많은 함수와 변수가 필요하다.

'객체지향프로그래밍'은 위와 같은 상황을 공유한다.
원리 또는 개념에 가까운 모델(모듈)을 생성한 뒤, 각 상황에 맞는 효율을 지정하기만 한다면, 쉽고 빠르게 제작할 수 있다.

class Person = {
    constuctor(tool, power, height) {
    	this.tool = tool
        this.power = power
        this.height = height
    }
    effi() {
    	return this.power * this.tool * this.height;
    }    
}

객체 지향 프로그래밍:

OOP의 목적은 복잡한 현실 세계의 현상들을 전자기기 세계 속에서 구현하기 위한 설계라고 볼 수 있으며, 네 가지 특징을 가지고 있다.

1) 캡슐화(Encapsulation)

캡슐화는 '압축시킨다'라고 이해하면 좋을 듯 하다. 위 단어는 영문권에서도 잘 사용하지 않는 어휘이지만(수능에서는 환영할 듯 하다), 다행히 우리는 '캡슐'이라는 단어에 익숙하고, 이를 '화(化)'한다는 의미도 잘 알고있다.

그럼 무엇을 압축시키는가? 바로 '절차지향프로그래밍'을 번거로운 상황을 '객체지향프로그래밍'으로 압축한다고 볼 수 있다.

'절차지향프로그래밍'에서는 개인에 따른 tool1, tool2, ..., tool100
power1, power2, ..., power100 height1, height2, ..., height100 effi1, efii2, ..., effi100 등의 변수와 함수를 만들어줘야 한다.
변수 3개와 함수 1개를 100명에 대해서 만드려면 400번의 수행이 필요하다. 만약 변수가 10여개, 함수가 10여개 정도 된다면...?

이를 압축시켜서 만들 수 있는 것이 '객체지향프로그래밍'이다. 비록 여러명이 생기면 그만큼 여러번 진행을 해야겠지만, 그래도 사람 수 만큼만 필요하게 된 것이다. 시간과 에너지 측면에서 정말 혁신적이라고 볼 수 밖에 없다.

2) 추상화(Abstraction)

추상화는 오늘날 '인터페이스'의 개념이라고 볼 수 있을 듯 하다. 인터페이스는 우리가 전자기기를 사용하는데 있어 보여지는 단면이다. 그리고 그 단면 속에서 우리는 간단한 조작으로 원하는 작업을 할 수 있다. 하지만 우리는 이러한 작업을 하는 가운데 전자기기 내부에서 일어나는 복잡한 메커니즘 따위는 신경쓰지 않는다.

마치 우리가 운전을 할 때 엑셀 패달을 밟으면 앞으로 나아가고, 브레이크 패달을 밟으면 멈추는 것을 알지만, 왜 패달을 밟으면 가고 멈추는 메커니즘 따위는 모른다. 그저 밟을 뿐이다.

이러한 추상화 작업은 특히 객체의 '행위(method)' 또는 '함수'에서 잘 나타난다.

자바스크립트를 예로, 우리는 객체(object)와 배열(array)의 (map, forEach, reduce 등)다양한 메서드를 알고 있다. 그리고 그 이면에서는 여러 조건문(if)과 반복문(for, while)의 조합을 통해서 이루어진 것을 유추할 수 있다.

만약 이러한 메서드가 없었다면, 우리는 매번 원하는 함수를 실행하기 위해서 다시금 그 함수를 만들어주는 고된 작업을 해야했지만, OOP를 통해서 매번 수월하게 메서드를 사용할 수 있게 됐다.

3) 상속(Inheritance)

객체는 자신을 포함하고 있는 상위 객체로부터 변수(properties)와 메서드(method) 등을 물려받아 사용할 수 있다. 이를 상속이라고 하며, 개인적으로는 '유전'의 의미와도 비슷하다고 생각된다. 이는 OOP 목적인 '재사용'을 훌륭히 해냈다고도 볼 수 있다.

4) 다형성(Polymorphism)

한 가지 모델 또는 모듈을 두고 다양한 모습으로 사용할 수 있게 한다. 이 또한 '재사용' 목적에 부합하는 특성으로써, 여러가지 상황에 따라 자신이 원하는 모양으로 사용할 수 있다.

Instantiation Patterns

OOP 로직을 재사용해 제작된 객체들을 '인스턴스'라고 부른다. 인스턴스를 생성하는(Instantiation) 다양한 방법들이 존재한다. 그 중 대표적으로 Fuctional Functional Shared Prototypal Pesudoclassical 방법이 있다. 이중에서 우리는 Pseudoclassical한 방법에 대해서 다루고자 한다.

자바스크립트는 ES6 업데이트를 통해 OOP에 최적화 된 class 라는 키워드를 배포했다. 위의 방법들은 class 키워드가 제공되기 전의 원시적인 방법들이다.

위의 예시를 다시 가져와서

class Person = {
    constuctor(tool, power, height) {
    	this.tool = tool
    	this.power = power
    	this.height = height
    }
    effi() {
    	return this.power * this.tool * this.height;
    }    
}

이 코딩은 class 키워드를 사용한 방법이다. 이를 Pseudoclassical 방법으로 변경하자면 아래와 같다.

let Person = function(tool, power, height) {
    this.tool = tool
    this.power = power
    this.height = height
}
Person.prototype.effi = function() {
	return this.tool * this.power * this.height;
}

그리고 class 키워드처럼 new라는 operator를 이용해 새로운 인스턴스를 제작할 수 있다.

let person1 = new Person(5, 3, 180)
let person1 = new Person(10, 2, 175)

Inheritance Patterns

우리는 이제 ES6 환경에 맞추어 개발을 진행하겠지만, 일부 남아있는 구(舊) 세대 코드들과 소통해야 하기 때문에, classPseudoclassical 환경 속에서 상속이 코드 안에서 어떻게 이루어지는지 살펴본다.

Pseudoclassical - Inheritance Patterns

간단한 예시를 설정하고 코드를 작성하면 좋을 듯 하다.
원시 시대에 집을 짓는다고 가정하고, 집을 짓는 코드 House를 만든다.

function House(material){
    this.material = material;
}
House.prototype.build = function() { 
    console.log('Im working now...'); 
}

이렇게 어떻게 재료를 가지는 집을 선언할 수 있고, 집을 짓는 명령어도 선언할 수 있게 됐다. 그럼 House라는 부모에 속해있는 '움막' Hut이라는 자식 객체를 만들어보자!

function Hut(material) {
    House.call(this, material)
}
Hut.prototype = Object.create(House.prototype);
Hut.prototype.constructor = Hut
Hut.prototype.done = function() {
    console.log('Almost done!');
}
Hut.prototype.build = function() {
   House.prototype.apply(this)
   console.log('I need more grass...')
}

위 코드를 살펴보면,
(1) House.call(this, material)
Hut 내부에서 부모 객체로부터 상속받기 위한 설정

(2) Hut.prototype = Object.creat(House.prototype)
부모의 prototype을 받기 위한 설정

하지만 이렇게되면, Hut는 더 이상 Hut이 아닌 House의 복제품이 되버린다.

(3) Hut.prototype.constructor = Hut
서로 다른 정체성을 주기 위해, constructor를 자기 자신으로 설정한다. 이렇게 되면, 기본적인 셋팅은 완료됐다.

(4) 새로운 메소드 선언 및 기존 메소드 사용
Hut.prototype.done과 같이 새로운 메소드를 선언할 수 있을 뿐만 아니라, Hut.prototype.build처럼 House 부모로부터 build 메소드를 상속받고, 이에 대해 추가적인 작업 또한 가능하다.

Class - Inheritance Patterns

위의 모습을 class 키워드로 변경하면 어떻게 될까? 먼저 House 설정 같은 경우 아래와 같이 변경할 수 있다.

class House {
   constructor(material) {
   	this.material = material;
   }
   build() {
	console.log('Im working now...');
   }
}

수도클래시컬 방법과 유사하지만, class라는 키워드 안에 constructor와 method를 한번에 처리할 수 있다는 점이 보다 효율적이다.

그렇다면 class 키워드 안에서 상속은 어떻게 이루어질까? extends 키워드를 이용하면 된다!

class Hut extends House {
    constructor(material) {
    	super(material);
    }
    build() {
    	super.build();
    	console.log('I need more grass...');
    }
    done() {
    	console.log('Almost done!');
    }
}

super 키워드: class 를 만능으로 만들어주는 마법과도 같은 키워드다.
모든 상속 받고 싶은 곳에서 그냥 super를 외치면 된다.
*단, 항상 super를 먼처 외쳐야 한다.

인스턴스 생성 시 생성자에서 super를 외치면, extends에서 불러오는 객체를 불러준다. 메서드에서도 마찮가지다.

또한 this를 사용하기 위해서는 super를 사용한 뒤에 사용할 수 있다. 그렇지 않으면 참조 오류가 발생한다.

0개의 댓글