객체지향 프로그래밍이란?

김형민·2021년 5월 31일
0

객체지향 프로그래밍(OOP - Object Oriented Programming), 함수형 프로그래밍(FP - Functional Programming) 모두 프로그래밍 패러다임의 종류 중 하나이다. 프로그래밍 패러다임을 사용하는 목적은 아래와 같다.

  1. Clear + Understandable
  2. Easy to Extend
  3. Easy to Maintain
  4. Memory Efficient
  5. DRY(Do not Repeat Yourself)

객체지향 vs 절차지향

절차지향 프로그래밍

  • 프로그램이 여러 개들의 함수로 구성된다. 함수, 변수에 각각 데이터가 저장된다.
  • 짜기 쉽고, 간결하지만 프로그램이 커지면서 함수가 많아질 수 록 복사하는 코드들이 많아지고 하나의 함수를 고치면 줄줄이 연속으로 다른 함수도 고쳐야 하는 상황이 발생한다.
  • 일명 스파게티 코드

객체지향 프로그래밍

  • 객체란 쉽게 말해서 관련된 변수(variables)과 해당 변수들을 조작하는 함수(function)들을 한 unit으로 묶은 것

OOP vs. FP

모든 프로그래밍 언어의 동작을 위한 구성요소는 크게 두가지이다.

1) 데이터(state)
2) 동작(function)

OOP, 즉 객체지향 프로그래밍은 '데이터'와 '동작'을 한 곳(객체)에 모아놓아서 더 이해하기 쉬운 코드를 만들고자 하는 프로그래밍 기법이다. 반면 FP, 함수지향 프로그래밍은 '데이터'와 '동작'은 서로 다른 것이므로 따로 관리하여 구분짓는 프로그래밍 기법이다.

이 둘의 관계는 vs.의 관계가 아니다. 무엇이 더 우위에 있는 것도 아니며 서로 상호보완적이기 때문에 내가 직면한 문제에 따라서 여러가지 멀티 패러다임을 다양하게 활용하는 것이 자바스크립트의 정수이다.

객체지향 프로그래밍 입문

let dragon = {
  name: 'Tanya',
  fire: true,
  fight() {
    return 5;
  }
}

위 코드의 사례가 바로 객체지향 프로그래밍의 전형적인 패턴이다. dragon이라는 객체 안에 dragon의 특성을 설명해주는 name과 fire라는 데이터(state)를 보관하고 있고, 동시에 fight라는 dragon 객체가 취해야 하는 동작(function)도 함께 보관하고 있다. 이처럼 객체 내의 데이터(state)를 통해 해당 객체의 상태를 계속 추적할 수 있으며, 함수를 통해 객체의 상태(state)를 변경하면서 유기적으로 상호작용 하고 있다.

OOP를 향한 1단계 : Encapsulation

간단한 게임을 만든믄데, 게임에는 Elf, Ogre, Knight, Hunter 등 다양한 캐릭터가 존재한다고 가정해보자. 만약 사용자가 Elf 캐릭터를 하나 만든다고 할 때, 얼마나 많은 반복 작업이 필요할지 아래 코드를 통해 상상해보자.

const elf = {
  name: 'Orwell',
  weapon: 'bow',
  attack() {
    return `attack with ${elf.weapon}`;
  }
}

elf.attack(); // 'attack with bow'

첫번째 유저가 elf 캐릭터를 생성했다. 그런데 만약 두번째 유저가 곧바로 elf 캐릭터를 또 생성한다면?

const elf2 = {
  name: 'Sally',
  weapon: 'bow',
  attack() {
    return `attack with ${elf2.weapon}`;
  }
}

이 코드들은 name과 weapon이라는 데이터(state)와 데이터를 이용한 동작인 attack 함수(function)을 elf2라는 하나의 객체 안에 보관하고 있다. 이러한 개념을 Encapsulation이라고 한다. 무언가를 구현하기 위해 필요한 데이터와 동작을 하나의 객체에 보관한 것이다. 그러나 문제는 만약 1억개의 elf 캐릭터를 생성해야 한다면? 이 코드를 1억번 복사 붙여넣기 한 후, name과 weapon을 모두 일일이 수정해주어야 할 것이다.

OOP를 향한 2단계 : Factory Functions

function createElf(name, weapon) {
  return {
    name: name,
    weapon: weapon,
    attack() {
      return 'attack with ' + weapon;
    }
  }
}

const peter = createElf('Peter', 'stones');
peter.attack(); // 'attack with stones'
const sam = createElf('Sam', 'stones');
sam.attack(); // 'attack with fire'

객체를 생성해주는 함수를 만들어서, 유저가 추가될 때마다 반복적인 여러 줄의 코드를 복사하는 대신 1회 함수 실행으로 대신하고 있으며, 매개변수를 받도록 하고 있다. 하지만 이 경우에 메모리 공간의 낭비가 발생하고 있다. name과 weapon은 모든 캐릭터마다 다르기 때문에 캐릭터 생성마다 매번 새롭게 설정하여 메모리 공간에 저장하는 것이 맞지만, attack 메소드의 경우 캐릭터의 이름과 무기가 어떻든 관계 없이 동일한 동작이다. 그러나 매 캐릭터마다 이 메소드가 따로 메모리에 저장되고 있기 때문에 중복이 발생하고 있는 것이다.

이를 개선하기 위해 기존에 배운 Prototypal Inheritance, 프로토타입 상속 개념을 활용해보자.

OOP를 향한 3단계 : Object.create()

const elfFunctionsStore = {
  attack() {
    return 'attack with ' + this.weapon;
  }
}

function createElf(name, weapon) {
  return {
    name: name,
    weapon: weapon
  }
}

const peter = createElf('Peter', 'stones');
peter.attack = elfFunctionsStore.attack;
peter.attack(); // 'attack with stones'
const sam = createElf('Sam', 'stones');
sam.attack = elfFunctionsStore.attack;
sam.attack(); // 'attack with fire'

모든 elf가 동일하게 공유하는 attack 함수를 하나의 변수에 담아둔 뒤, 캐릭터 생성 후 해당 함수를 캐릭터의 특성에 부여했다. 하지만 이 또한 여러차례의 수작업이 필요하다. Object.create()를 이용해서 개선해보자.

const elfFunctionsStore = {
  attack() {
    return 'attack with ' + this.weapon;
  }
}

function createElf(name, weapon) {
  let newElf = Object.create(elfFunctionsStore);
  newElf.name = name;
  newElf.weapon = weapon;
  return newElf;
}

const peter = createElf('Peter', 'stones');
peter.attack(); // 'attack with stones'
const sam = createElf('Sam', 'fire');
sam.attack(); // 'attack with fire'

엘프를 생성할 때, 최초에 attack 함수가 저장되어 있는 객체를 상속받도록 지정했다. (Object.create 관련 MDN 문서 참조) 덕분에 많은 코드의 반복이 줄어들었다.

출처 : https://soldonii.tistory.com/78

profile
항해 중인 개발자

0개의 댓글