객체 지향 프로그래밍

계리·2024년 2월 25일
0

최근에 학습한 것들이 과연 객체 지향 프로그래밍적으로 학습을 했을까? 단순히 자바로 객체를 만들어서 객체를 사용한 것이 객체 지향 프로그래밍일까? 지금까지 학습한 것은 절차 지향 프로그래밍이다. 객체를 만들어서 사용하는 것이 객체 지향 프로그래밍이 아니라는 것이다. 최근에 학습한 것은 좋은 절차 지향 프로그래밍이였다.

절차 지향 프로그래밍

  • 말 그대로 코드가 절차적으로 진행되어 프로그래밍 되는 것, 실행 순서를 중요시 한다라는 의미로 해석할 수 있다.
  • 프로그램의 흐름을 순차적으로 처리하는 방식이다. 즉 "어떻게"를 중심으로 프로그래밍 한다.

객체 지향 프로그래밍

  • 말 그대로 객체 지향적으로 프로그래밍을 한다, 객체를 중요하게 생각한다.
  • 실제 세계의 사물이나 사건을 객체로 보고 이러한 객체들 간에 서로 상호작용을 중심으로 프로그래밍 한다. 즉 "무엇을" 중심으로 프로그래밍 한다.

객체란

세상 모든 사물을 추상화해보면 속성과 기능 2가지로 설명할 수 있다.


차이점

절차 지향 프로그래밍은 데이터와 데이터 처리에 대한 방식이 분리되어 있다. 객체 지향 프로그래밍은 데이터와 그 데이터에 대한 행동(메서드)이 하나인 객체 안에 포함되어 있다.


절차 지향 프로그래밍 예시 1

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
음악 플레이어를 종료합니다

코드를 보면 절차적으로 실행 순서대로 프로그램이 작동했다.


절차 지향 프로그래밍 예시 2

package oop1;

public class MusicPlayerData {
	int volume;
	boolean isOn;
}

package oop1;

public class MusicPlayerMain2 {

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

MusicPlayerData 클래스를 생성해서 기존 로직을 변경했다. 이제는 기존 변수가 변경되거나 다양한 변수가 생겨도 객체를 통해 쉽게 가져다가 사용할 수 있다.


절차 지향 프로그래밍 예시 3

위에 코드를 보면 볼륨 증가하는 부분이 중복이 되어 있다. 그리고 위에 코드들의 기능들은 재사용할 가능성이 높다

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

각 기능들은 메서드로 생성해서 모듈화 시켜 필요할 때마다 가져다가 사용하면 되고 이로 인한 장점들이 생겼다.

  • 중복 제거 : 로직에서 중복이 제거되어 같은 로직이 필요하면 해당 메서드를 사용하면 된다.
  • 변경 영향 범위 : 메서드 안에 내용만 수정하면 된다.
  • 메서드 이름 추가 : 메서드 이름을 통해 코드를 더 쉽게 이해할 수 있다.

처음 코드에는 메인 메서드(MusicPlayerMain1)에 변수와 기능들을 코드로 순차적으로 작성 했었고 점차적으로 객체를 통해 인스턴스를 생성하고 기능들을 메서드로 묶어서 코드를 좀 더 보완했다.

하지만 이렇게 객체를 통해 인스턴스를 생성했다고 하지만 위에서 객체 지향 프로그래밍에 대해 얘기한 것중에 데이와 그 데이터의 행동(메서드)이 해당 객체 안에 다 같이 포함되어 있어야 한다고 했다.

MusicPlayerData 클래스 안에 데이터(변수)만 있고 데이터의 행동(메서드)은 MusicPlayerMain3 메인 클래스 안에 있다.

이렇게 나뉘어져 있다면 MusicPlayerData 클래스에서 멤버변수들의 추가 또는 수정을 하게 된다면 MusicPlayerMain3 메인 클래스에서도 추가 또는 수정이 이루어져야 하므로 관리 포인트가 2곳으로 늘어나게 된다.

이러한 문제점들을 보완하기 위해서 객체 지향 프로그래밍으로 데이터와 해당 데이터들로 기능을 객체 클래스 안에 포함시켜서 인스턴스를 생성하면 해당 인스턴스로 가져다가 사용만 하면 된다. 이러한 예시 코드를 작성 해보려고 한다.


클래스와 메서드

클래스 안에 메서드를 생성할 수 있지만 먼저 비교를 위해 멤버변수만 있는 클래스를 생성한다.

예시

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

ValueData 인스턴스를 생성해서 ValueData.value에 접근해서 add 메서드로 값을 증가시키는 코드이다. 그런데 add 메서드는 ValueData 클래스와 분리되어 있는데 포함시키는 코드를 바로 작성 정의해보자.


package oop1;

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

ValueObject 클래스에서 멤버변수 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.add 메서드에도 접근을 할 수 있게 되었다.

valueObject.add(); //1
x002.add(); //2: x002 ValueObject 인스턴스에 있는 add() 메서드를 호출한다.
  1. add 메서드 호출 후 value 변수도 호출을 하는데 기본으로 본인 인스턴스에 있는 멤버 변수에 접근한다. 그래서 x002에 접근해서 x002.value에 접근한다.

  2. ++ 연산으로 값을 증가시킨다.


정리

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

객체 지향 프로그래밍 예시

위에서 음악플레이어 프로그램을 절차 지향적으로 정의한 것을 객체 지향적으로 변경해서 정의했다.

package oop1;

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 클래스 내부에 어떤 속성(멤버 변수)이 있는지 몰라도 MusicPlayer 클래스에서 제공하는 기능만 가져다가 사용하면 된다.

캡슐화

객체를 구성하기 위해 속성(데이터 또는 멤버 변수)과 기능을 하나의 캡슐처럼 쌓여있는 것 같다. 이렇게 속성과 기능을 하나로 묶어서 필요할 때마다 외부에서 사용하는 것을 캡슐화라고 한다.


참고

  • 김영한의 실전 자바 기본편
profile
gyery

0개의 댓글