frontend/javascript

🟨 1-14. 객체지향 설계 심화 — 캡슐화, 상속, 추상화, 다형성을 자바스크립트로 구현하기

mirabo01 2025. 11. 6. 22:12

1. 객체지향의 4대 핵심 원칙

객체지향 프로그래밍(OOP)은 단순히 클래스를 쓰는 게 아니다.
진짜 핵심은 “설계 철학”이다.
자바스크립트에서도 이 철학은 그대로 적용된다.

원칙 설명 자바스크립트 적용 방식

캡슐화 (Encapsulation) 데이터와 메서드를 한곳에 묶고, 외부 접근을 제한 private 필드, getter/setter
상속 (Inheritance) 기존 코드를 재사용하여 새로운 클래스 생성 extends, super()
추상화 (Abstraction) 불필요한 세부 구현은 숨기고 핵심만 노출 클래스/인터페이스 구조
다형성 (Polymorphism) 같은 메서드가 다른 방식으로 동작 메서드 오버라이딩, 인터페이스 구현

2. 캡슐화(Encapsulation) — 내부 데이터를 보호하라

캡슐화는 “데이터를 직접 조작하지 못하게 하고, 메서드로만 접근하게 만드는 것”이다.

class BankAccount {
  #balance = 0; // private field

  deposit(amount) {
    if (amount <= 0) throw new Error("금액은 0보다 커야 합니다.");
    this.#balance += amount;
  }

  withdraw(amount) {
    if (amount > this.#balance) throw new Error("잔액이 부족합니다.");
    this.#balance -= amount;
  }

  get balance() {
    return this.#balance;
  }
}

const acc = new BankAccount();
acc.deposit(1000);
acc.withdraw(400);
console.log(acc.balance); // 600

이렇게 #으로 선언된 필드는 클래스 내부에서만 접근 가능하다.
외부에서 acc.#balance를 접근하면 에러가 발생한다.

즉, 클래스의 내부 구현을 숨기고,
필요한 기능만 ‘공식 인터페이스’로 제공하는 것이 캡슐화다.


3. getter / setter — 안전한 데이터 접근

캡슐화와 함께 자주 쓰이는 개념이 getter/setter다.
이건 속성처럼 보이지만 실제로는 함수다.

class User {
  #name;

  constructor(name) {
    this.#name = name;
  }

  get name() {
    return this.#name;
  }

  set name(value) {
    if (value.length < 2) throw new Error("이름은 두 글자 이상이어야 합니다.");
    this.#name = value;
  }
}

const user = new User("기범");
console.log(user.name); // 기범
user.name = "민수"; // setter 실행
console.log(user.name); // 민수

이런 구조는 외부에서 잘못된 값을 직접 넣는 것을 방지하고,
필요하면 내부적으로 추가 로직(검증, 로그 기록 등)을 수행할 수 있다.


4. 상속(Inheritance) — 코드를 재사용하라

이전 편에서도 봤듯이, extends 키워드로 부모 클래스의 기능을 자식 클래스가 물려받을 수 있다.

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name}이(가) 소리를 냅니다.`);
  }
}

class Dog extends Animal {
  speak() {
    console.log(`${this.name}이(가) 멍멍 짖습니다.`);
  }
}

const dog = new Dog("해피");
dog.speak(); // 해피이(가) 멍멍 짖습니다.

부모의 생성자에 접근할 때는 반드시 super()를 호출해야 한다.

class Cat extends Animal {
  constructor(name, color) {
    super(name);
    this.color = color;
  }
}

상속은 중복 코드를 줄이고, 공통 로직을 한 번만 정의할 수 있도록 한다.


5. 추상화(Abstraction) — 불필요한 복잡성을 감춰라

자바스크립트에는 “추상 클래스”나 “인터페이스” 같은 문법이 없다.
하지만 다음과 같이 구현할 수 있다.

class Shape {
  area() {
    throw new Error("하위 클래스에서 area()를 구현해야 합니다.");
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius ** 2;
  }
}

const circle = new Circle(5);
console.log(circle.area()); // 78.5398...

Shape 클래스는 설계만 제공하고, 실제 구현은 하위 클래스에서 담당한다.
이게 바로 “추상화”의 핵심이다.


6. 다형성(Polymorphism) — 같은 인터페이스, 다른 동작

같은 메서드 이름을 갖지만, 각 클래스마다 다르게 동작할 수 있다.

class Shape {
  draw() {
    console.log("도형을 그립니다.");
  }
}

class Circle extends Shape {
  draw() {
    console.log("원을 그립니다.");
  }
}

class Rectangle extends Shape {
  draw() {
    console.log("사각형을 그립니다.");
  }
}

const shapes = [new Circle(), new Rectangle()];
shapes.forEach(shape => shape.draw());

출력 결과:

원을 그립니다.
사각형을 그립니다.

즉, 같은 draw() 메서드라도 객체의 타입에 따라 다르게 실행된다.
이것이 다형성의 본질이다.


7. 실무 예시 — UI 컴포넌트 추상화

프론트엔드에서 이런 객체지향 설계는 실제로 UI 컴포넌트 구조 설계에 자주 쓰인다.

class Component {
  constructor(id) {
    this.el = document.getElementById(id);
  }

  render() {
    throw new Error("render()는 반드시 구현해야 합니다.");
  }
}

class Button extends Component {
  constructor(id, label) {
    super(id);
    this.label = label;
  }

  render() {
    this.el.innerHTML = `<button>${this.label}</button>`;
  }
}

class Input extends Component {
  constructor(id, placeholder) {
    super(id);
    this.placeholder = placeholder;
  }

  render() {
    this.el.innerHTML = `<input placeholder="${this.placeholder}" />`;
  }
}

const btn = new Button("app", "클릭!");
btn.render();

이런 구조는 React의 “컴포넌트 추상화” 원리와 매우 유사하다.
Component가 추상 클래스 역할을 하며, 각 파생 클래스는 구체적인 UI를 구현한다.


8. 상속보다 조합(Composition)을 고려하라

실무에서는 상속이 남용되면 오히려 유지보수가 어려워진다.
그래서 최근에는 “조합(Composition over Inheritance)” 철학이 더 많이 쓰인다.

즉, “상속으로 확장하기보다 필요한 기능을 합성하자.”

const clickable = {
  onClick() {
    console.log("클릭됨!");
  },
};

const draggable = {
  onDrag() {
    console.log("드래그 중...");
  },
};

function createButton(name) {
  return {
    name,
    ...clickable,
    ...draggable,
  };
}

const button = createButton("버튼");
button.onClick();
button.onDrag();

이런 식으로 여러 기능을 조립하면 훨씬 유연한 구조를 만들 수 있다.
React 훅(Hooks)이나 Vue 믹스인(Mixins)도 같은 철학을 따른다.


9. 정리

개념 핵심 역할 자바스크립트 구현 방식

캡슐화 데이터 보호 private 필드, getter/setter
상속 코드 재사용 extends, super()
추상화 복잡성 감춤 메서드 미구현, 공통 인터페이스 제공
다형성 같은 메서드, 다른 동작 오버라이딩
조합 유연한 구조 객체 병합, 믹스인 패턴

10. 마무리

객체지향은 단순히 “class를 쓰는 문법”이 아니다.

그것은 “코드를 사람처럼 사고하는 방식”이다.

자바스크립트는 느슨하지만 강력한 객체지향 언어다.
캡슐화로 데이터를 보호하고,
상속으로 로직을 재사용하며,
추상화로 복잡성을 감추고,
다형성으로 유연하게 대응한다.

결국 이 모든 구조는
확장성 있고 예측 가능한 코드”를 만드는 데 목적이 있다.


다음 편에서는
1-15. 모듈화 + 객체지향 설계로 프로젝트 구조 설계하기 — 폴더 구조부터 설계 철학까지
를 통해 지금까지 배운 모든 개념(모듈, 클래스, 상속, 캡슐화)을
하나의 실무 프로젝트 구조로 엮어볼 거야.