[☕JAVA] 객체 지향 프로그래밍(OOP)
이번 포스팅에서는 절차 지향 프로그래밍에서 출발해 점진적으로 코드를 개선하며 진정한 객체 지향 프로그래밍이 무엇인지 익혀먹어 보자.
절차 지향 vs. 객체 지향 🤔
프로그래밍 패러다임은 크게 두 가지로 나눌 수 있다.
- 절차 지향 프로그래밍 (Procedural Programming)
- 실행 순서, 즉 “어떻게” 동작할지에 초점을 맞추는 방식
- 데이터와 그 데이터를 처리하는 기능(함수)이 서로 분리되어 있음
- 객체 지향 프로그래밍 (Object-Oriented Programming)
- 객체, 즉 “무엇을” 만들지에 초점을 맞추는 방식
- 데이터(속성)와 그 데이터를 처리하는 기능(메서드)이 하나의 ‘객체’ 안에 함께 묶여 있음
음악 플레이어를 만드는 과정을 통해 두 방식의 차이를 직접 경험해 보자
절차 지향으로 음악 플레이어 만들기 🎧
먼저 가장 단순한 절차 지향 방식으로 코드를 작성해 보자.
데이터와 모든 로직이 main
메서드 안에 순서대로 나열됨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// MusicPlayerMain1
public static void main(String[] args) {
int volume = 0;
boolean isOn = false;
//음악 플레이어 켜기
isOn = true;
System.out.println("음악 플레이어를 시작합니다");
//볼륨 증가
volume++;
System.out.println("음악 플레이어 볼륨:" + volume);
// ... (중략) ...
//음악 플레이어 끄기
isOn = false;
System.out.println("음악 플레이어를 종료합니다");
}
이 방식은 코드가 짧을 땐 직관적이지만, 기능이 복잡해질수록 데이터와 로직이 얽혀 유지보수가 매우 어려워진다!
개선 1단계: 데이터 묶기
관련 데이터(volume
, isOn
)를 MusicPlayerData
라는 클래스로 묶어보자. 이렇게 하면 데이터 관리가 조금 더 용이해진다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// MusicPlayerData.java
public class MusicPlayerData {
int volume = 0;
boolean isOn = false;
}
// MusicPlayerMain2.java
public static void main(String[] args) {
MusicPlayerData data = new MusicPlayerData();
// 음악 플레이어 켜기
data.isOn = true;
// 볼륨 증가
data.volume++;
//...
}
개선 2단계: 메서드 추출
이제 각 기능을 별도의 메서드로 추출해서 모듈화해보자. 이렇게 하면 코드의 중복을 제거하고, 각 기능의 역할을 명확히 알 수 있다.
모듈화: 쉽게 이야기해서 레고 블럭과 비슷하다. 필요한 블럭을 가져다 꼽아서 사용하는 매커니즘과 같음.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// MusicPlayerMain3.java
public static void main(String[] args) {
MusicPlayerData data = new MusicPlayerData();
on(data);
volumeUp(data);
// ...
}
static void on(MusicPlayerData data) {
data.isOn = true;
System.out.println("음악 플레이어를 시작합니다");
}
static void volumeUp(MusicPlayerData data) {
data.volume++;
System.out.println("음악 플레이어 볼륨:" + data.volume);
}
//...
코드가 훨씬 깔끔해졌지만, 이것은 여전히 절차 지향의 한계를 가진다.
데이터는 MusicPlayerData
객체에, 기능은 MusicPlayerMain3
의 메서드에 분리되어 있지만, 데이터와 기능이 따로 놀기 때문에 여전히 관리 포인트가 두 곳이다.
객체 지향 프로그래밍: 속성과 기능을 하나로
객체 지향의 핵심: 데이터(속성)와 기능(메서드)을 하나의 클래스 안에 온전히 묶는 것
MusicPlayer
클래스 안에 플레이어의 속성(volume
, isOn
)과 기능(on()
, off()
, volumeUp()
등)을 모두 넣어보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// MusicPlayer.java
public class MusicPlayer {
int volume = 0;
boolean isOn = false;
void on() {
isOn = true;
System.out.println("음악 플레이어를 시작합니다");
}
void off() {
isOn = false;
System.out.println("음악 플레이어를 종료합니다");
}
void volumeUp() {
volume++;
System.out.println("음악 플레이어 볼륨:" + volume);
}
void volumeDown() {
volume--;
System.out.println("음악 플레이어 볼륨:" + volume);
}
void showStatus() {
System.out.println("음악 플레이어 상태 확인");
if (isOn) {
System.out.println("음악 플레이어 ON, 볼륨:" + volume);
} else {
System.out.println("음악 플레이어 OFF");
}
}
}
이제 이 MusicPlayer
를 사용하는 코드는 매우 단순하고 직관적으로 변하는 걸 볼 수 있다.
1
2
3
4
5
6
7
8
9
10
11
// MusicPlayerMain4.java
public static void main(String[] args) {
MusicPlayer player = new MusicPlayer();
player.on();
player.volumeUp();
player.volumeUp();
player.volumeDown();
player.showStatus();
player.off();
}
MusicPlayer
를 사용하는 쪽에서는 이제 volume
이나 isOn
같은 내부 데이터에 전혀 신경 쓸 필요가 없다!
그저 player
라는 객체가 제공하는 기능(on()
, volumeUp()
등)을 호출하기만 하면 된다.
이처럼 속성과 기능을 하나의 캡슐처럼 묶어서 외부에 필요한 기능만 제공하는 것을 캡슐화(Encapsulation)라고 한다.
왜 객체 지향이 좋은가? ✨
- 직관적인 코드: 실제 세상의 사물을 다루는 것처럼 프로그래밍할 수 있어 코드가 더 친숙하고 이해하기 쉽다
- 높은 유지보수성: 기능이 변경되어도 해당 객체의 내부만 수정하면 된다. 객체를 사용하는 쪽의 코드는 바꿀 필요가 없어 변경의 영향 범위가 적다.
객체 지향 프로그래밍은 단순히 데이터를 묶는 것을 넘어, 프로그램을 더 유연하고 확장 가능하게 만드는 강력한 패러다임이다!
핵심 정리
- 절차 지향: 데이터와 기능이 분리되어 있고, 프로그램의 실행 순서에 초점을 둠
- 객체 지향: 데이터(속성)와 기능(메서드)을 하나의 객체 안에 묶고, 객체 간의 상호작용에 초점을 둠
- 캡슐화: 객체 지향의 핵심 특징으로, 속성과 기능을 하나로 묶어 내부 구현을 숨기고 외부에 필요한 기능만 제공