[디자인 패턴] 0. 들어가기

김주희's avatar
Jul 21, 2025
[디자인 패턴] 0. 들어가기

0. 컴퓨터 시스템 구조

HW → OS → APP → JVM → 실행 흐름까지 단계별로 이해해보자.
 

1. HW, OS, APP의 구조

컴퓨터 시스템은 크게 3가지 층으로 나눌 수 있다:
┌──────────────────────────────┐ │ Application (Slack, Chrome…) │ ← 우리가 개발하는 앱 ├──────────────────────────────┤ │ OS (Windows, macOS, Linux…) │ ← 하드웨어와 앱을 중개 ├──────────────────────────────┤ │ HW (CPU, RAM, SSD…) │ ← 실제 기계 └──────────────────────────────┘
1. HW (Hardware)
  • 컴퓨터의 실제 부품들: CPU, RAM, SSD, 키보드, 모니터 등
  • 모든 명령어는 **0과 1의 이진수(binary)**로 전달되어야 한다.
2. OS (Operating System)
  • Windows, macOS, Linux처럼 하드웨어와 앱 사이를 중개하는 소프트웨어
  • 키보드나 파일 접근 등 복잡한 하드웨어 제어를 대신 수행해준다.
  • OS는 명령어를 이해하기 위한 **자기만의 언어(Shell)**를 갖고 있다. (dir, cd 등)
3. APP (Application)
  • 우리가 개발하는 프로그램(ex. 게임, 웹 서비스)
  • 보통은 Java, Python 같은 고수준 언어로 작성된다.
 

2. 자바 프로그램이 실행되는 전체 흐름

자바는 특별하다. 바로 **JVM(Java Virtual Machine)**이라는 중간층이 존재하기 때문이다. 이 덕분에 자바는 플랫폼 독립적으로 실행된다.
 
일반적인 실행 흐름 (1) .java 파일 → [컴파일] → (2) .class 파일 → [실행] → (3) JVM → (4) OS → (5) HW
  1. .java 파일: 우리가 작성하는 소스코드 (영어 기반의 고수준 언어)
  1. .class 파일: 컴파일을 거쳐 만들어진 바이트코드 (JVM이 이해함)
  1. JVM 실행: .class 파일을 해석해서 OS에 맞게 명령을 전달
  1. OS가 명령 실행: 실제 하드웨어를 제어할 수 있는 OS 레벨의 명령 실행
  1. HW 동작: 모니터에 출력하거나, 파일을 저장하거나, CPU가 계산 수행
 

3. JVM이 중요한 이유

JVM은 자바 프로그램을 운영체제에 종속되지 않도록 해준다.
즉, 한 번 작성한 코드가 Windows, macOS, Linux 어디서든 실행될 수 있게 해주는 핵심 역할
 

4. C 언어와 비교하면?

C언어는 OS에 맞게 직접 컴파일된다. 그래서 Windows용으로 만든 C 프로그램은 Linux에서 실행되지 않는다. OS 종속적이다.
C 언어 → OS에 맞게 컴파일 → 실행파일 생성 → OStightly-coupled
하지만 Java는?
Java JVM 위한 .class 생성 → JVM이 OS 맞게 실행 → OS 독립적 (Write once, Run anywhere)
 

5. 요약

계층
설명
예시
HW
실제 기계 부품
CPU, RAM, SSD
OS
HW 제어를 중개
Windows, macOS
APP
우리가 만든 프로그램
게임, 웹 서비스
JVM
자바 코드 실행을 중개
Java Virtual Machine
컴파일
java → class 변환
javac 명령
실행
JVM이 바이트코드를 실행
java 명령
 

1. 메모리 구조 이해하기: Static, Heap, Stack

프로그래밍을 하다 보면 자주 마주치는 개념이 있다. 바로 Static, Heap, Stack 메모리다. 이 세 가지는 JVM(Java Virtual Machine) 혹은 일반적인 프로그래밍 언어에서도 중요한 메모리 구조로, 각각의 목적과 쓰임이 분명히 다르다.

1. Static 영역: 프로그램 시작과 함께

Static은 프로그램이 시작될 때 메모리에 올라가고, 종료될 때까지 유지된다. 예를 들어 태양처럼, 지구(프로그램)의 존재와 관계없이 처음부터 끝까지 존재해야 하는 정보들을 static 메모리에 올려두는 것이다. Java에서는 static 키워드로 선언된 변수나 메서드가 이 영역에 저장된다.
예시
public class Earth { static String sun = "태양"; // 프로그램 시작부터 종료까지 유지 }

2. Heap 영역: 원할 때 생성되는 공간

Heap은 객체 인스턴스가 저장되는 공간이다. 우리가 new 키워드로 객체를 생성하면, 그 객체는 heap 메모리에 저장된다. 생성 시점부터 소멸 시점까지 살아있고, 개발자가 원할 때 객체를 생성하고 제거할 수 있다.
예시
Animal dog = new Animal(); // new 키워드로 heap에 올라감

3. Stack 영역: 메서드 실행 중 잠깐 사용

Stack은 메서드가 호출될 때 생기고, 메서드가 끝나면 사라진다. 예를 들어 attack() 메서드를 실행하면, 그 순간 필요한 값(예: 각도 계산, 임시 변수 등)이 스택에 저장되고, 메서드 실행이 끝나면 모두 사라진다. 메서드별로 stack 공간이 할당되며 같은 메서드를 두 번 연속하여 실행하더라도 다른 stack 공간이 할당된다. 짧은 생명 주기를 가진 메모리 공간이다.
예시
public void attack() { int angle = 20; // stack에 저장됨 // 공격 로직 수행 }
 
정리:
  • Static: 프로그램 시작부터 종료까지
  • Heap: new로 객체 생성 시
  • Stack: 메서드 실행 중 일시적으로 생성
 

4. 필기

notion image
 

5. 전체 소스 코드

package ex00; // static, heap, stack class Animal { static String name = "강아지"; // static 공간에 name 뜸 void speak() { String sound = "멍멍"; // stack 공간에 sound 잠깐 뜸 System.out.println(sound); } } public class Mem01 { public static void main(String[] args) { System.out.println(Animal.name); // static 공간에 name 찾음 Animal a = new Animal(); // heap 생성 (speak 메모리에 뜸) a.speak(); } }
 
 

2. 동적 바인딩 (추상화)

"자동차는 엔진인가?" 라는 질문을 받는다면 우리는 "아니다"라고 답할 것이다. 이처럼 현실 세계에서 ‘상속’은 적절하지 않은 상황이 많다. 그렇다면 자동차와 엔진은 어떤 관계로 설계해야 할까? 정답은 컴포지션(Composition)이다.

1. 상속 vs 컴포지션

  • 상속(Inheritance): "A는 B이다(is-a)" 관계일 때 사용.
    • 예: 강아지는 동물이다 → Dog extends Animal
  • 컴포지션(Composition): "A는 B를 가진다(has-a)" 관계일 때 사용.
    • 예: 자동차는 엔진을 가진다 → Car has Engine

2. 추상 클래스와 추상화

추상 클래스는 객체를 생성할 수 없다. 오직 상속을 통해 구체적인 클래스에서 구현되도록 설계된 구조다. 예를 들어 Car가 추상 클래스라면, Sonata, Genesis 같은 실제 차량이 이를 상속받아 구현한다.
예시
abstract class Car { abstract void run(); // 추상 메서드 } class Sonata extends Car { @Override void run() { System.out.println("Sonata run"); } }

3. 동적 바인딩(Dynamic Binding)

Car 타입의 변수로 SonataGenesis 객체를 가리킬 수 있을 때, 실제 실행 시점에 어떤 클래스의 run()이 실행될지는 동적으로 결정된다. 이것이 동적 바인딩이다.
Car car = new Sonata(); // 타입은 Car, 객체는 Sonata car.run(); // Sonata의 run()이 호출됨
이런 구조의 핵심은, 공통된 부모 타입(Car)을 통해 다양한 자식 클래스(Sonata, Genesis)를 하나의 타입으로 다룰 수 있다는 점이다. 설계의 유연성과 확장성을 크게 높여준다.

4. 추상화를 왜 할까?

  • 여러 객체를 공통된 부모 타입으로 묶어 일괄 처리할 수 있다.
  • 메서드를 재정의(override)하여 객체마다 다르게 동작하도록 할 수 있다.
  • 유지보수가 쉬워진다. 새로운 객체가 추가되어도 기존 코드 수정 없이 확장 가능하다.

✨ 마무리 요약

개념
설명
Static
프로그램 시작과 함께 떠서 종료 시까지 유지되는 메모리
Heap
객체를 new할 때 메모리에 올라가는 공간
Stack
메서드 실행 중 임시로 생기는 메모리 공간
상속
"is-a" 관계 (예: Dog is an Animal)
컴포지션
"has-a" 관계 (예: Car has an Engine)
추상 클래스
객체 생성 불가, 자식 클래스에서 구현 필요
동적 바인딩
실행 시점에 실제 메서드 결정 (재정의 기반)
추상화
여러 객체를 공통된 타입으로 묶어 설계하는 방법
 
notion image
package ex00; // 동적 바인딩 abstract class Car { abstract void run(); } class Genesis extends Car { // 재정의 void run() { System.out.println("Genesis run"); } } class Sonata extends Car { // 재정의 void run() { System.out.println("Sonata run"); } } public class Mem02 { public static void main(String[] args) { Car car1 = new Sonata(); car1.run(); Car car2 = new Genesis(); car2.run(); } }
 
Share article

jay0628