icon
6장 : 자바스크립트 심화 I

객체 지향과 함수형 프로그래밍

우리는 지금까지 자바스크립트의 기본적인 문법과 동작 원리, 그리고 객체와 프로토타입을 이용한 데이터 관리 방법에 대해 깊이 있게 학습했습니다. 이제 코드를 작성하는 도구와 원리를 넘어, 코드를 어떤 '방식'으로 조직하고 설계할 것인가에 대한 논의가 필요합니다. 이것이 바로 프로그래밍 패러다임(Programming Paradigm) 입니다.

프로그래밍 패러다임은 프로그래머가 프로그램을 작성할 때 따르는 일련의 사고방식이나 원칙을 의미합니다. 이는 문제 해결 방식과 코드 구조에 큰 영향을 미치며, 어떤 패러다임을 선택하느냐에 따라 코드의 가독성, 유지보수성, 확장성 등이 달라질 수 있습니다.

자바스크립트는 매우 유연한 언어여서 단일한 패러다임에 얽매이지 않고, 여러 패러다임의 특징을 수용할 수 있습니다. 특히 현대 자바스크립트 개발에서는 객체 지향 프로그래밍(Object-Oriented Programming, OOP)함수형 프로그래밍(Functional Programming, FP) 이라는 두 가지 주요 패러다임의 특징이 널리 사용됩니다.

이번 장에서는 이 두 가지 핵심 프로그래밍 패러다임의 기본적인 개념과 자바스크립트에서의 적용 방식을 함께 살펴보겠습니다. 각 패러다임의 장단점을 이해하고, 적절한 상황에 맞춰 활용하는 안목을 기르는 것이 목표입니다.


객체 지향 프로그래밍

객체 지향 프로그래밍(OOP) 은 실제 세계의 개체(객체)를 모델링하여 프로그램을 설계하는 패러다임입니다. 데이터(속성)와 데이터를 처리하는 기능(메서드)을 하나의 '객체' 안에 캡슐화하고, 이 객체들 간의 상호작용을 통해 프로그램을 구성합니다.

OOP의 핵심 원칙은 다음과 같습니다.

  1. 캡슐화 (Encapsulation)

    • 데이터와 데이터를 조작하는 메서드를 하나의 단위(객체)로 묶는 것입니다.
    • 객체의 내부 구현을 외부로부터 숨기고, 미리 정의된 인터페이스(메서드)를 통해서만 접근하도록 합니다. 이는 코드의 응집도를 높이고, 외부 변경으로부터 내부를 보호하여 유지보수를 쉽게 합니다.
    • 자바스크립트에서는 클로저를 활용하거나, ES6 class 문법에서 #을 이용한 프라이빗 필드를 통해 유사하게 구현할 수 있습니다.
    // class를 이용한 캡슐화 예시
    class Account {
        #balance; // ES2020 private class fields (최신 문법)
    
        constructor(initialBalance) {
            if (initialBalance < 0) {
                throw new Error("초기 잔액은 0 이상이어야 합니다.");
            }
            this.#balance = initialBalance;
        }
    
        deposit(amount) {
            if (amount > 0) {
                this.#balance += amount;
                console.log(`입금: ${amount}, 현재 잔액: ${this.#balance}`);
            }
        }
    
        withdraw(amount) {
            if (amount > 0 && this.#balance >= amount) {
                this.#balance -= amount;
                console.log(`출금: ${amount}, 현재 잔액: ${this.#balance}`);
            } else {
                console.log("잔액 부족 또는 유효하지 않은 금액.");
            }
        }
    
        getBalance() {
            return this.#balance;
        }
    }
    
    const myAccount = new Account(10000);
    myAccount.deposit(5000);
    myAccount.withdraw(2000);
    console.log(myAccount.getBalance());
    // console.log(myAccount.#balance); // 직접 접근 불가 (SyntaxError)
  2. 상속 (Inheritance)

    • 부모 객체(클래스)의 속성과 메서드를 자식 객체(클래스)가 물려받아 재사용하거나 확장하는 기능입니다.
    • 코드의 재사용성을 높이고, 계층적인 구조를 통해 시스템을 모델링하는 데 유리합니다.
    • 자바스크립트에서는 프로토타입 체인이나 class extends 키워드를 통해 구현합니다.
    // 상속 예시 (이전 3장에서 다룬 class 상속)
    class Vehicle {
        constructor(brand) {
            this.brand = brand;
        }
        start() {
            console.log(`${this.brand} 시동을 겁니다.`);
        }
    }
    
    class Car extends Vehicle {
        constructor(brand, model) {
            super(brand);
            this.model = model;
        }
        drive() {
            console.log(`${this.brand} ${this.model}가 운전 중입니다.`);
        }
    }
    
    const myCar = new Car("현대", "쏘나타");
    myCar.start(); // 부모 클래스의 메서드 사용
    myCar.drive(); // 자식 클래스의 메서드 사용
  3. 추상화 (Abstraction)

    • 복잡한 시스템의 세부 구현을 숨기고, 사용자에게 필요한 핵심 기능만을 노출하는 것입니다.
    • 사용자는 객체가 '무엇을 하는지'는 알지만, '어떻게 하는지'는 알 필요가 없게 만듭니다.
    • 이는 복잡성을 관리하고, 시스템을 더 쉽게 이해하고 사용할 수 있도록 돕습니다.
  4. 다형성 (Polymorphism)

    • 같은 이름의 메서드가 객체의 종류에 따라 다르게 동작하는 능력입니다.
    • 즉, 다양한 형태(다형)를 가질 수 있다는 의미로, 코드를 더 유연하고 확장 가능하게 만듭니다.
    • 자바스크립트에서는 메서드 오버라이딩(자식 클래스에서 부모 메서드 재정의) 등을 통해 구현됩니다.
    class Animal {
        sound() {
            console.log("동물 소리!");
        }
    }
    
    class Cat extends Animal {
        sound() { // 부모의 sound 메서드를 재정의 (오버라이딩)
            console.log("야옹!");
        }
    }
    
    class Dog extends Animal {
        sound() { // 부모의 sound 메서드를 재정의 (오버라이딩)
            console.log("멍멍!");
        }
    }
    
    const animals = [new Animal(), new Cat(), new Dog()];
    
    animals.forEach(animal => animal.sound());
    /*
    결과:
    동물 소리!
    야옹!
    멍멍!
    */
    // 동일한 sound() 메서드를 호출했지만, 객체 타입에 따라 다른 동작을 보입니다.

OOP의 장점

  • 재사용성: 클래스와 상속을 통해 코드를 재사용하고 확장하기 용이합니다.
  • 유지보수성: 캡슐화를 통해 코드 변경의 영향을 최소화할 수 있습니다.
  • 생산성: 잘 설계된 객체는 독립적으로 개발하고 테스트할 수 있습니다.
  • 확장성: 새로운 기능을 추가하기 용이하며, 기존 코드를 덜 변경하고도 기능을 확장할 수 있습니다.

OOP의 단점

  • 설계에 많은 시간과 노력이 필요할 수 있습니다.
  • 클래스 계층 구조가 복잡해지면 이해하기 어려워질 수 있습니다.
  • 상속의 깊이가 깊어지면 특정 변화가 예기치 않은 곳에서 문제를 일으킬 수 있습니다 (상속의 문제).

함수형 프로그래밍

함수형 프로그래밍(FP) 은 프로그램을 순수 함수(Pure Function) 들의 조합으로 생각하고, 데이터의 불변성(Immutability)을 강조하는 패러다임입니다. 상태 변경과 부작용(Side Effect)을 최소화하여 코드의 예측 가능성을 높이고 버그 발생 가능성을 줄이는 데 중점을 둡니다.

FP의 핵심 개념은 다음과 같습니다.

  1. 순수 함수 (Pure Function)

    • 동일한 입력에 대해 항상 동일한 출력을 반환합니다.
    • 함수 외부의 어떤 상태도 변경하지 않습니다. (Side Effect 없음)
    • 함수 외부의 변수를 수정하거나, 콘솔에 출력하거나, 네트워크 요청을 보내는 등의 행위는 부작용으로 간주됩니다.
    // 순수 함수 (Pure Function)
    function add(a, b) {
        return a + b; // 항상 동일한 입력에 동일한 출력, 외부 상태 변경 없음
    }
    
    // 순수하지 않은 함수 (Impure Function) 예시
    let total = 0;
    function addToTotal(num) {
        total += num; // 외부 변수 total을 변경 (부작용)
        return total;
    }
    // console.log() 또한 부작용입니다.

    순수 함수는 테스트하기 용이하고, 병렬 처리 환경에서 안전하며, 코드의 예측 가능성을 높입니다.

  2. 불변성 (Immutability)

    • 데이터를 한 번 생성하면 절대로 변경하지 않습니다.
    • 데이터를 변경해야 할 필요가 있을 때는 원본 데이터를 복사하여 새로운 데이터를 생성하고 변경된 내용을 적용합니다.
    • 이는 코드의 안정성을 높이고, 시간 경과에 따른 데이터의 변화를 추적하기 쉽게 만듭니다.
    // 불변성 유지 (배열 복사)
    const originalArray = [1, 2, 3];
    const newArray = [...originalArray, 4]; // 전개 구문으로 새로운 배열 생성
    
    console.log(originalArray); // 결과: [1, 2, 3] (원본은 변경되지 않음)
    console.log(newArray);    // 결과: [1, 2, 3, 4]
    
    // 불변성 유지 (객체 복사)
    const originalUser = { name: "Alice", age: 30 };
    const updatedUser = { ...originalUser, age: 31 }; // 새로운 객체 생성
    
    console.log(originalUser); // 결과: { name: 'Alice', age: 30 } (원본은 변경되지 않음)
    console.log(updatedUser);  // 결과: { name: 'Alice', age: 31 }
  3. 고차 함수 (Higher-Order Functions)

    • 함수를 인자로 받거나, 함수를 반환하는 함수를 의미합니다.
    • 자바스크립트의 map, filter, reduce 등이 대표적인 고차 함수입니다. 이는 함수형 프로그래밍에서 매우 중요한 개념입니다.
    const numbers = [1, 2, 3, 4, 5];
    
    // map: 배열의 각 요소에 함수를 적용하여 새로운 배열 반환 (불변성 유지)
    const squaredNumbers = numbers.map(num => num * num);
    console.log(squaredNumbers); // 결과: [1, 4, 9, 16, 25]
    
    // filter: 조건에 맞는 요소만 걸러내어 새로운 배열 반환 (불변성 유지)
    const evenNumbers = numbers.filter(num => num % 2 === 0);
    console.log(evenNumbers); // 결과: [2, 4]
    
    // reduce: 배열의 모든 요소를 하나의 값으로 축소
    const sum = numbers.reduce((accumulator, current) => accumulator + current, 0);
    console.log(sum); // 결과: 15

FP의 장점

  • 예측 가능성: 순수 함수와 불변성으로 인해 코드의 동작을 예측하기 쉽습니다.
  • 테스트 용이성: 부작용이 없어 단위 테스트 작성이 매우 용이합니다.
  • 병렬 처리: 데이터 변경이 없으므로 병렬 프로그래밍에 유리합니다.
  • 모듈성: 함수 단위로 코드가 잘 분리되어 재사용성이 높습니다.

FP의 단점

  • 데이터 불변성을 유지하기 위한 추가적인 복사 작업이 성능에 영향을 줄 수 있습니다.
  • 복잡한 상태 관리가 필요한 경우, 데이터 흐름이 다소 복잡해질 수 있습니다.
  • 익숙하지 않은 개발자에게는 초기에 학습 곡선이 높을 수 있습니다.

자바스크립트에서의 멀티 패러다임 활용

자바스크립트는 객체 지향적 특성(객체 리터럴, 프로토타입, class)과 함수형 특성(함수가 일급 객체, 클로저, 고차 함수)을 모두 강력하게 지원하는 멀티 패러다임(Multi-paradigm) 언어입니다. 따라서 개발자는 상황과 문제의 특성에 따라 두 패러다임을 유연하게 조합하여 사용할 수 있습니다.

  • 객체 지향: 복잡한 비즈니스 로직이나 실제 세계의 개체를 모델링할 때, 대규모 시스템의 구조를 잡을 때 유용합니다. 컴포넌트 기반의 UI 개발(React, Vue 등)에서도 객체 지향적 사고방식이 많이 활용됩니다.
  • 함수형: 데이터 처리, 변환, 상태 관리의 예측 가능성이 중요할 때, 순수하고 독립적인 유틸리티 함수를 만들 때 강력합니다. 특히 웹 애플리케이션의 상태 변화를 관리하거나, 비동기 작업을 처리할 때 함수형 접근이 빛을 발합니다.

효율적이고 가독성 높은 자바스크립트 코드를 작성하기 위해서는 이 두 패러다임의 장점을 이해하고, 어느 한쪽에 치우치기보다는 문제 해결에 가장 적합한 방식을 선택하여 조합하는 능력을 기르는 것이 중요합니다.


마무리하며

이번 장에서는 자바스크립트 코드를 설계하고 구성하는 주요 사고방식인 프로그래밍 패러다임에 대해 학습했습니다. 특히 객체 지향 프로그래밍(OOP) 의 캡슐화, 상속, 추상화, 다형성 원칙과, 함수형 프로그래밍(FP) 의 순수 함수, 불변성, 고차 함수 개념을 상세히 살펴보았습니다.

자바스크립트가 멀티 패러다임 언어라는 점을 이해하고, 각 패러다임이 가진 장점과 단점을 바탕으로 실제 개발에서 어떻게 활용될 수 있는지 그 가능성을 보았습니다. 특정 문제에 직면했을 때, "이것을 객체로 모델링하는 것이 좋을까?", "아니면 데이터를 변경하지 않는 순수 함수들의 조합으로 해결하는 것이 더 효율적일까?"와 같은 질문을 스스로 던져보며 사고의 폭을 넓히는 것이 중요합니다.

이러한 프로그래밍 패러다임에 대한 이해는 단순히 문법을 아는 것을 넘어, '좋은 코드'를 작성하는 개발자로 성장하는 데 필수적인 지식입니다. 이제 여러분은 자바스크립트의 깊은 원리와 다양한 활용 방식에 대한 탄탄한 기반을 다졌습니다.