객체지향 프로그래밍의 구조 이해하기

Nogglee·2026년 3월 13일

객체지향 철학을 왜 이해해야할까?

자바스크립트는 객체 기반(Object-based) 언어이기 때문에 객체의 생성 원리와 구조를 이해하는 것이 중요하다.
객체지향 개념을 이해하면 코드 구조를 더 명확하게 설계할 수 있고, 복잡한 프로그램을 구성할 때 데이터와 기능을 논리적으로 묶어 관리할 수 있다.

또, 현대 웹 개발에서 널리 사용되는 TypeScript와 React 역시 객체지향적 개념과 구조를 기반으로 동작한다.
따라서 객체지향 철학을 이해하는 것은 프로그램 설계 방식 자체를 이해하는 과정이라고 볼 수 있다.

구조적 프로그래밍

구조적 프로그래밍은 프로그램을 함수 중심으로 분해하여 설계하는 방식이며,
이 방식은 구조가 단순한 프로그램에서는 효율적이지만, 프로그램이 커질수록 데이터와 기능이 분리되어 관리가 어려워질 수 있다.

구조적 프로그래밍 특징

  • 프로그램이 순차적, 하향식(Top-down) 구조로 설계된다.
  • 기능 단위의 기본 구성 요소는 함수(function) 이다.
  • 문제를 작은 기능 단위로 나누어 해결한다.

객체지향 프로그래밍

객체지향 프로그래밍(OOP)은 프로그램을 객체 중심으로 설계하는 방식이다.
하나의 프로그램에 버튼, 창, 메뉴와 같은 객체들이 존재한다고 가정하였을 때,
해당 객체들이 이벤트를 통해 서로 동작하면서 프로그램이 실행된다.

객체지향 프로그래밍 특징

  • 프로그램의 기본 단위는 객체(Object) 이다.
  • 객체는 데이터(속성)와 기능(메서드)을 함께 포함한다.
  • 객체와 객체가 서로 상호작용하면서 프로그램이 동작한다.

추상화

추상화는 여러 객체에서 공통적인 특징만을 추출하여 개념화하는 과정이다.
복잡한 현실 세계의 대상에서 핵심적인 속성만 남기고 불필요한 세부 사항을 제거하는 것이다.

현실 세계에는 푸들, 포메라니안, 골든 리트리버 등 다양한 종류의 개가 존재한다.
하지만 이들을 하나의 개념으로 묶으면 개(Dog) 라는 추상적인 개념을 만들 수 있다.

추상화의 철학적 배경

추상화 개념은 철학에서도 등장한다.
고대 철학자 플라톤은 현실 세계의 개별적인 사물들 뒤에는 본질적인 형태(이데아) 가 존재한다고 보았다.

우리가 보는 다양한 개들은 모두 서로 다르지만 그 뒤에는 개라는 본질적인 개념 이 존재한다.
위와 같은 개념을 이데아(idea) 라고 하며, 이러한 사고 방식이 객체지향에서의 추상화 개념과 연결된다.

객체지향에서 추상화의 의미

객체지향 프로그래밍에서는 현실 세계의 객체를 모델링할 때 추상화를 사용한다.
이 방식은 다음과 같은 장점이 있다.

  • 코드 구조가 명확해진다
  • 재사용성이 높아진다
  • 복잡한 시스템을 단계적으로 설계할 수 있다

캡슐화

캡슐화는 데이터를 외부로부터 숨기고 필요한 기능만 공개하는 객체지향 설계 원칙이다.
단순히 데이터를 숨기는 것이 아니라 데이터와 이를 처리하는 기능을 하나의 단위로 묶어 보호하는 것을 의미한다.

캡슐화 목적

  • 데이터 보호
  • 객체 내부 구현 은닉
  • 안정적인 인터페이스 제공

객체지향에서 클래스는 클래스 = 데이터 + 메소드 와 같은 구조를 가진다.
예를 들어 객체 내부에는 은닉된 데이터가 존재하고, 외부에서는 멤버 함수(메소드) 를 통해서만 접근할 수 있다.
이 구조는 실제 캡슐처럼 내용물을 보호하는 구조와 유사하다.


클래스

클래스는 객체를 만들기 위한 설계도이다.
사전적인 의미로는 분류 또는 집합 이라는 뜻을 가지며,
프로그래밍에서는 사용자가 정의한 새로운 데이터 타입을 의미한다.
즉 사용자 정의 데이터 타입(User Defined Data Type)인 것이다.

클래스 특징

  • 데이터와 기능을 함께 포함한다
  • 객체 생성을 위한 템플릿 역할을 한다
  • 하나의 추상적인 데이터 타입이다

클래스 구성요소

멤버 변수 (Field)
객체의 상태(State) 를 나타낸다.
'사람'을 객체로 표현하자면, 이름, 나이, 와 같은 것들이 멤버 변수가된다.

멤버 함수 (Method)
객체가 수행할 수 있는 행동(Behavior) 을 나타낸다.
'사람'을 객체로 표현하자면, 걷는다, 말한다, 공부한다 와 같은 것들이 멤버 함수가된다.

이와 같이 현실 세계의 객체를 분석하여 '상태'는 변수로 '행동'은 메소드 로 표현하는 과정이 객체 모델링이다.

클래스 선언 구조

클래스는 다음과 같은 형태로 선언한다.

class 클래스이름
{
    접근지정자 데이터타입 멤버변수;
    접근지정자 데이터타입 메소드();

    생성자
    소멸자
}

접근 지정자

접근 지정자는 클래스 내부 데이터에 대한 접근 범위를 제어하는 기능이다.
대표적인 접근 지정자는 다음과 같다.

구분설명
public모든 곳에서 접근 가능
protected상속 관계에서만 접근 가능
internal같은 프로젝트 내부에서 접근 가능
private클래스 내부에서만 접근 가능

특히 private 은 캡슐화를 구현할 때 가장 중요한 접근 지정자이다.


객체

클래스를 통해 선언된 변수를 객체(Object) 라고 한다.

class Dog
{
    private char pudle, pome, jindo;
    public void bark();
}

위와 같은 클래스 선언문에서 Dog → 클래스, a → 객체 이다.
즉 객체는 클래스로부터 생성된 실체(instance) 이다.

객체 생성 과정

객체는 일반적으로 다음과 같이 생성된다.

Dog a = new Dog();

이 과정에서 메모리 구조는 다음과 같이 구성된다.

  • Stack 영역 -> 객체를 참조하는 변수(a)가 저장된다.
  • Heap 영역 -> 실제 객체 데이터가 생성된다.

Stack의 변수는 Heap에 생성된 객체를 참조(reference) 하게 된다.

Stack 영역       Heap 영역

   a의 값          Dog의 주소
 ---------       ---------  
| 0x1234  | --> | 0x1234  |
 ---------       ---------  
|         |     | 0x5678  |
 ---------       ---------  
|         |     | 0x1234  |
 ---------       ---------  

인터페이스

인터페이스는 메서드의 목록만 정의하고 실제 구현은 포함하지 않는 명세(Specification) 이다.
즉 인터페이스는 어떤 기능이 존재해야 하는지만 정의하고, 실제 동작은 구현 클래스에서 작성한다.

인터페이스 특징

  • 메서드 선언만 존재한다
  • 메서드 구현은 포함하지 않는다
  • 사용자 정의 타입으로 사용된다
  • 클래스가 인터페이스를 구현한다

인터페이스 선언

인터페이스는 다음과 같은 형태로 정의된다.

interface Phone
{
    void call();
    void message();
    void wifi();
}

접근지정자 interface 인터페이스이름
{
    메서드 선언;
}

인터페이스 구현

인터페이스를 사용하는 클래스는 해당 메서드를 반드시 구현해야 한다.

class Smartphone : Phone
{
    public void call() { Console.WriteLine("통화 기능 실행"); }
    public void message() { Console.WriteLine("문자 메시지 실행"); }
    public void wifi() { Console.WriteLine("와이파이 연결"); }
}

인터페이스 사용 목적

인터페이스는 다음과 같은 목적을 가진다.

  1. 기능의 표준 정의
    여러 클래스가 동일한 기능을 구현할 수 있도록 공통 규격을 제공한다.
  2. 기능 확장
    기존 코드를 수정하지 않고 새로운 구현을 추가할 수 있다.
  3. 공동 작업에서 규격 제공
    아키텍트 -> 인터페이스 정의 / 실무자 → 인터페이스 구현 이런 방식으로 작업 분리가 가능하다.

생성자

모든 변수는 선언 후 반드시 초기화가 필요하다.
객체 역시 클래스 기반으로 생성된 하나의 변수이기 때문에 초기화 과정이 필요하다.
객체가 생성될 때 초기화를 담당하는 특별한 메서드를 생성자(Constructor) 라고 한다.

생성자 특징

  • 객체 생성 시 자동으로 실행된다.
  • 객체의 초기값을 설정하는 역할을 한다.
  • 클래스 이름과 동일한 이름을 가진다.
  • 반환 타입이 존재하지 않는다.
class Dog
{
    public Dog()
    {
        // 객체 초기화
    }
}

// 객체 생성 시 생성자가 자동 호출된다.
Dog a = new Dog();

위 코드가 실행되면 내부적으로 다음 과정이 발생한다.

  1. Heap 영역에 객체 메모리 생성
  2. 생성자 호출
  3. 객체 초기화 완료

상속성

상속은 이미 만들어진 클래스를 기반으로 새로운 클래스를 만드는 객체지향 개념이다.
부모가 자식에게 재산을 상속하듯, 객체지향에서도 부모 클래스의 속성과 기능을 자식 클래스가 물려받는다.

계층 구조

상속은 아래와 같이 보통 계층 구조(Hierarchy) 형태를 가진다.

생물
 ├ 동물
 │   ├ 개
 │   └ 고양이
 └ 식물
     ├ 장미
     └ 개나리

상위 하이라키로 올라갈수록 추상적인 개념이고
하위 하이라키로 내려갈수록 구체적인 개념이 된다.

상속의 문법

상속은 다음과 같이 표현된다.

// C# 방식
class Dog : Animal
{
}

접근지정자 클래스이름 : 부모클래스
{
}
// Java 방식
class Dog extends Animal
{
}

접근지정자 클래스이름 extends 부모클래스
{
}

상속의 장점

상속을 사용하는 이유는 다음과 같다.

  1. 코드 재사용
    이미 작성된 클래스를 재사용할 수 있다.
  2. 유지보수 용이
    공통 기능을 부모 클래스에서 관리할 수 있다.
  3. 확장성
    기존 클래스를 수정하지 않고 새로운 기능을 추가할 수 있다.

프로젝트 관점에서의 상속

실제 프로젝트에서는 다음과 같은 상황에서 상속이 활용된다.

새로운 프로젝트 B가 시작되었는데, A 프로젝트 기능과 유사한 구조를 가진다고 가정한다.
이 경우 새로운 B 프로젝트는 기존 A 프로젝트 기능을 상속받고 새로운 기능만 추가하면 된다.

B 프로젝트
 ├ A 프로젝트 기능 1,2,3,4
 └ 추가 기능 5,6

위와 방식은 코드 중복을 줄이고 개발 속도를 높인다.


다형성

객체지향 프로그래밍에서 다형성(Polymorphism) 은 하나의 인터페이스나 이름이 상황에 따라 여러 형태로 동작할 수 있는 특성을 의미한다.
대표적으로 오버로딩(Overloading) 과 오버라이딩(Overriding) 두 가지 방식으로 구현된다.

오버로딩 (Overloading)

오버로딩은 같은 이름의 함수라도 전달 인자의 타입이나 개수가 다르면 서로 다른 함수로 동작하도록 정의하는 것을 의미한다.
함수 이름은 같지만 매개변수(parameter)가 다르면 다른 함수로 구분된다.

사전적 의미로 ‘과적하다’, ‘적재하다’ 라는 의미를 가지며,
프로그래밍에서는 하나의 이름에 여러 기능을 담는 것을 의미한다.

오버로딩의 특징

  • 함수 이름이 동일하다
  • 매개변수 타입 또는 개수가 달라야 한다
  • 반환 타입만 다른 경우는 오버로딩이 성립하지 않는다
  • 같은 클래스 내부에서 사용된다
class Calculator
{
    int add(int a, int b) { return a + b; }
    double add(double a, double b) { return a + b; }
    int add(int a, int b, int c) { return a + b + c; }
}

위의 예시 함수는 모두 같은 이름의 함수지만 매개변수가 다르기 때문에 다른 함수로 동작한다.

오버로딩의 목적

  • 코드 가독성 향상
  • 동일 기능을 같은 이름으로 묶어 관리
  • 사용 편의성 증가

오버라이딩 (Overriding)

오버라이딩은 부모 클래스에서 정의된 메서드를 자식 클래스에서 재정의하여 사용하는 것을 의미한다.
부모 클래스의 기능을 자식 클래스에서 새롭게 구현하여 덮어쓰는 것이라는 개념이다.
오버라이딩은 상속 관계가 반드시 존재해야 한다.

오버라이딩의 특징

  • 부모 클래스와 자식 클래스 사이에서 발생
  • 메서드 이름이 동일하다
  • 매개변수도 동일해야 한다
  • 기존 부모 메서드를 덮어쓴다
class Animal {
    void sound() {
        System.out.println("동물이 소리를 낸다");
    }
}

class Dog extends Animal {
    void sound() {
        System.out.println("멍멍");
    }
}

Animal.sound() → 기본 동작
Dog.sound() → 자식 클래스에서 재정의

오버로딩 vs 오버라이딩

구분오버로딩오버라이딩
개념같은 이름의 함수 여러 개 정의부모 메서드를 자식이 재정의
관계같은 클래스 내부상속 관계 필요
매개변수달라야 함동일해야 함
목적함수 사용 편의성기능 확장 및 재정의

람다식 (Lambda Expression)

람다식은 익명 메서드를 간결하게 표현하는 함수 표현식이다.
별도의 메서드를 정의하지 않고 필요한 위치에서 바로 함수를 작성하는 방식이다.

익명 메서드

익명 메서드는 이름이 없는 메서드이다.
메서드를 미리 정의하지 않아도 돼서 코드가 간결해지고 일회성 로직에 적합하다.

람다식 표현 형태

람다식 기본 구조는 아래와 같다.

(인수) => 표현식 또는 명령문
profile
Product-minded Engineer

0개의 댓글