DevOps/Java

객체 지향 프로그래밍(feat. 김영한의 실전 자바)

minseoki 2025. 10. 19. 15:43

절차 지향 프로그래밍 vs 객체 지향 프로그래밍

절차 지향 프로그래밍은 "어떻게"를 중심으로 프로그램을 작성한다.

즉, 실행 순서와 흐름에 초점을 맞춘다.

 

객체 지향 프로그래밍은 이름 그대로 객체를 지향한다. 

쉽게 이야기해서 객체를 중요하게 생각하는 방식이다.

객체 지향 프로그래밍은 실제 세계의 사물이나 사건을 객체로 보고, 이러한 객체들 간의 상호작용을 중심으로 프로그래밍하는 방식이다. 

즉, "무엇을" 중심으로 프로그래밍한다.

 

절차 지향으로 만든 음악 플레이어

package oop1;
public class MusicPlayerMain1 {
    public static void main(String[] args) {
         int volume = 0;
         boolean isOn = false;
		 //음악 플레이어 켜기
		 isOn = true;
		 System.out.println("음악 플레이어를 시작합니다");
		 //볼륨 증가
		 volume++;
		 System.out.println("음악 플레이어 볼륨:" + volume);
   	     //볼륨 증가
		 volume++;
		 System.out.println("음악 플레이어 볼륨:" + volume);
		 //볼륨 감소
		 volume--;
		 System.out.println("음악 플레이어 볼륨:" + volume);
		 //음악 플레이어 상태
		 System.out.println("음악 플레이어 상태 확인"); if (isOn) {
		 System.out.println("음악 플레이어 ON, 볼륨:" + volume); } else {
		 System.out.println("음악 플레이어 OFF"); }
		 //음악 플레이어 끄기
		 isOn = false;
		 System.out.println("음악 플레이어를 종료합니다");
	}
}

 

실행결과

음악 플레이어를 시작합니다 
음악 플레이어 볼륨:1
음악 플레이어 볼륨:2
음악 플레이어 볼륨:1
음악 플레이어 상태 확인 
음악 플레이어 ON, 볼륨:1 
음악 플레이어를 종료합니다

 

이 프로그램은 간단하지만 볼륨을 조절할 때마다 같은 코드가 반복되고 있다. 

코드의 양이 많아지고, 기능을 수정할 때 일일이 찾아서 변경해야 한다.

 

이게 바로 절차 지향의 한계이다.

 

메서드로 모듈화 하기

조금 더 나아가서 기능을 메서드로 분리해 보자.

 

절차 지향 프로그래밍 - 데이터 묶음, 메서드 추출

package oop1;
/**
* 메서드 추출 */
public class MusicPlayerMain3 {
		public static void main(String[] args) { 
				MusicPlayerData data = new MusicPlayerData(); //음악 플레이어 켜기
				on(data);
				//볼륨 증가
				volumeUp(data);
				//볼륨 증가
				volumeUp(data);
				//볼륨 감소
				volumeDown(data);
				//음악 플레이어 상태
				showStatus(data);
				//음악 플레이어 끄기
				off(data);
		}
		static void on(MusicPlayerData data) { data.isOn = true;
				System.out.println("음악 플레이어를 시작합니다");
		}
		static void off(MusicPlayerData data) { data.isOn = false;
				System.out.println("음악 플레이어를 종료합니다");
		}
	  static void volumeUp(MusicPlayerData data) {
        data.volume++;
				System.out.println("음악 플레이어 볼륨:" + data.volume);
		}
    static void volumeDown(MusicPlayerData data) {
        data.volume--;
				System.out.println("음악 플레이어 볼륨:" + data.volume); 
		}
		static void showStatus(MusicPlayerData data) { 
				System.out.println("음악 플레이어 상태 확인"); 
				if (data.isOn) {
				System.out.println("음악 플레이어 ON, 볼륨:" + data.volume); 
				} else {
				System.out.println("음악 플레이어 OFF"); 
				}
		} 
}

 

각각의 기능을 메서드로 만든 덕분에 기능이 모듈화 되었다. 덕분에 다음과 같은 장점이 생겼다.

  • 중복 제거: 로직 중복이 제거되었다. 같은 로직이 필요하면 해당 메서드를 여러 번 호출하면 된다.
  • 변경 영향 범위: 기능을 수정할 때 해당 메서드 내부만 변경하면 된다.
  • 메서드 이름 추가: 메서드 이름을 통해 코드를 더 쉽게 이해할 수 있다.

 

모듈화: 쉽게 이야기해서 레고 블럭을 생각하면 된다. 필요한 블럭을 가져다 꼽아서 사용할 수 있다. 여기서는 음악 플레이어의 기능이 필요하면 해당 기능을 메서드 호출만으로 손쉽게 사용할 수 있다. 

 

 

 

클래스와 메서드

클래스는 데이터인 멤버 변수뿐 아니라 기능 역할을 하는 메서드도 포함할 수 있다.

 

먼저 멤버 변수만 존재하는 클래스로 간단한 코드를 작성해 보자.

package oop1;

public class ValueData {
	int value;
}
package oop1;

public class ValueDataMain {
		
	public static void main(String[] args) {
		ValueData valueData = new ValueData();
        add(valueData);
		add(valueData);
		add(valueData);
		System.out.println("최종 숫자=" + valueData.value);
	}
	static void add(ValueData valueData) {
		valueData.value++;
		System.out.println("숫자 증가 value=" + valueData.value);
	}
}

 

실행 결과

숫자 증가 value=1 
숫자 증가 value=2 
숫자 증가 value=3 
최종 숫자=3

 

ValueDate라는 인스턴스를 생성하고 외부에서 ValueData.value에 접근해 숫자를 하나씩 증가시키는 단순한 코드이다. 코드를 보면 데이터인 value와 value의 값을 증가시키는 기능인 add() 메서드가 서로 분리되어 있다.

 

자바 같은 객체 지향 언어는 클래스 내부에 속성(데이터)과 기능(메서드)을 함께 포함할 수 있다. 클래스 내부에 멤버 변수뿐만 아니라 메서드도 함께 포함할 수 있다는 뜻이다.

 

이번에는 숫자를 증가시키는 기능도 클래스에 함께 포함해서 새로운 클래스를 정의해 보자.

 

package oop1;

public class ValueObject {

	int value;
		
	void add() {
		value++;
		System.out.println("숫자 증가 value=" + value);
	}
}

 

이 클래스에는 데이터인 value와 해당 데이터를 사용하는 기능인 add() 메서드를 함께 정의했다.

이제 이 클래스가 어떻게 사용되는지 확인해 보자.

 

참고) 여기서 만드는 add() 메서드에는 static 키워드를 사용하지 않는다.

메서드는 객체를 생성해야 호출할 수 있다. 그런데 static이 붙으면 객체를 생성하지 않고도 메서드를 호출할 수 있다.

 

package oop1;

public class ValueObjectMain {
		
	public static void main(String[] args {
		ValueObject valueObject = new ValueObject();
		valueObject.add();
		valueObject.add();
		valueObject.add();
		System.out.println("최종 숫자=" + valueObject.value);
	}
}

 

실행 결과

숫자 증가 value=1
숫자 증가 value=2
숫자 증가 value=3
최종 숫자=3

 

인스턴스 생성

ValueObject valueObject = new Value Object();

 

 

valueObject라는 객체를 생성했다.

이 객체는 멤버 변수뿐만 아니라 내부에 기능을 수행하는 add() 메서드도 함께 존재한다.

 

 

인스턴스의 메서드 호출

정리

  • 클래스는 속성(데이터, 멤버 변수)과 기능(메서드)을 정의할 수 있다.
  • 객체는 자신의 메서드를 통해 자신의 멤버 변수에 접근할 수 있다.
    • 객체의 메서드 내부에서 접근하는 멤버 변수는 객체 자신의 멤버 변수이다.

 

객체 지향 프로그래밍 - 음악 플레이어

package oop;

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");
		}
	}
}

 

package oop1;

public class MusicPlayerMain4 {

	public static void main(String[] args) {
		MusicPlayer player = new MusicPlayer();
		//음악 플레이어 켜기
		player.on();
		//볼륨 증가
		player.volumeUp();
		//볼륨 증가
		player.volumeUp();
		//볼륨 감소
		player.volumeDown();
		//음악 플레이어 상태
		player.showStatus();
		//음악 플레이어 끄기
		player.off();
	}
}

 

실행 결과

음악 플레이어를 시작합니다 
음악 플레이어 볼륨:1
음악 플레이어 볼륨:2
음악 플레이어 볼륨:1
음악 플레이어 상태 확인 
음악 플레이어 ON, 볼륨:1 
음악 플레이어를 종료합니다

 

MusicPlayer를 사용하는 코드를 보자.

MusicPlayer 객체를 생성하고 필요한 기능(메서드)을 호출하기만 하면 된다. 필요한 모든 것은 MusicPlayer 안에 들어있다.

  • MusicPlayer를 사용하는 입장에서는 MusicPlayer의 데이터인 volume, isOn 같은 데이터는 전혀 사용하지 않는다.
  • MusicPlayer를 사용하는 입장에서는 이제 MusicPlayer 내부에 어떤 속성(데이터)이 있는지 전혀 몰라도 된다. MusicPlayer를 사용하는 입장에서는 단순하게 MusicPlayer가 제공하는 기능 중에 필요한 기능을 호출해서 사용하기만 하면 된다.

 

캡슐화

MusicPlayer를 보면 음악 플레이어를 구성하기 위한 속성과 기능이 마치 하나의 캡슐에 쌓여있는 것 같다.

이렇게 속성과 기능을 하나로 묶어서 필요한 기능을 메서드를 통해 외부에 제공하는 것을 캡슐화라 한다.

 

객체 지향 프로그래밍 덕분에 음악 플레이어 객체를 사용하는 입장에서 진짜 음악 플레이어를 만들고 사용하는 것처럼 친숙하게 느껴진다. 그래서 코드가 더 읽기 쉬운 것은 물론이고, 속성과 기능이 한 곳에 있기 때문에 변경도 더 쉬워진다.