JAVA

JAVA - 객체 지향 프로그램

jonghyeon6084 2024. 1. 3. 11:22
728x90

1. 절차 지향 프로그램1 - 시작

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

절차 지향 프로그래밍

  • 절차 지향 프로그래밍은 이름 그대로 절차를 지향한다. 쉽게 이야기해서 실행 순서를 중요하게 생각하는 방식이다.
  • 절차 지향 프로그래밍은 프로그램의 흐름을 순차적으로 따르며 처리하는 방식이다. 즉 "어떻게"를 중심으로 프로그래밍 한다.

객체 지향 프로그래밍

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

둘의 중요한 차이

 - 절차 지향은 데이터와 해당 데이터에 대한 처리 방식이 분리되어 있다. 반면 객체 지향에서는 데이터와 그 데이터에 대한 행동(메서드)이 하나의 '객체'안에 함께 포함되어 있다.

 

앞서 작성했던 코드들은 모두 절차 지향 프로그램이다.

 

절차 지향 프로그램에서 객체 지향 프로그램으로 점진적으로 바꿔보는 걸로 하자.

 

음악 플레이어 만들기.

요구사항

  • 음악 플레이어를 켜고 끌 수 있어야 한다.
  • 음악 플레이어의 볼륨을 증가, 감소할 수 있어야 한다.
  • 음악 플레이어의 상태를 확인할 수 있어야 한다.
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("음악 플레이어를 종료합니다.");
    }

이 코드를 점진적으로 바꿔보자.

2. 절차 지향 프로그램2 - 데이터 묶음

위 코드에서 MusicPlayerData라는 클래스를 만들고, 음악 플레이어에 사용되는 데이터들을 해당 클래스에 묶어서 멤버 변수로 사용해보자.

public class MusicPlayerData {
	// 사실 여기서 volume과 isOn에 초기화를 안해줘도 상관없음.
    // 왜냐하면 멤버 변수는 알아서 선언만 해주면 초기화가 알아서 되기 때문
    int volume = 0;
    boolean isOn = false;
}
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("음악 플레이어를 종료합니다.");
}

3. 절차 지향 프로그램3 - 메서드 추출

위 코드를 보면 볼륨 증가, 볼륨 감소는 많이 반복이 되는 모습이다. 그리고

  • 음악 플레이어 켜기, 끄기
  • 볼륨 증가, 감소
  • 음악 플레이어 상태 출력

기능들은 재사용 될 가능성이 매우 높다. 메서드를 사용해서 기능 구분해보자.

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) {
    if (data.isOn) {
        System.out.println("음악 플레이어 ON, 볼륨 : " + data.volume);
    } else {
        System.out.println("음악 플레이어 OFF");
    }
}

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

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

모듈화란??

 - 쉽게 이야기해서 레코 블럭을 생각하면 된다. 필요한 블럭을 가져다 꼽아서 사용할 수 있다. 여기서는 음악 플레이어의 기능이 필요하면 해당 기능을 메서드 호출 만으로 손쉽게 사용할 수 있다. 이제 음악 플레이어와 관련된 메서드를 조립해서 프로그램을 작성할 수 있다.

 

절차 지향 프로그래밍의 한계

 - 데이터와 기능이 분리되어 있다는 점이다. 음악플레이어의 데이터는 MusicPlayerData의 데이터를 사용하고, 각각의 기능(메서드)은 MusicPlayerMain3의 각각 메서드를 사용해야 한다. 이 둘은 매우 밀접한 관련이 있는데 이렇게 분리되어 있으면 유지보수 관점에서도 관리 포인트가 늘어나기 때문에 유지보수에 좀 더 힘들어진다.

 - 데이터와 기능을 하나로 묶는다는 것이 어떤 의미인지 이해하기 위해 예제를 통해 살펴보자.

4. 클래스와 메서드

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

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

public class ValueData {
    int value;
}
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);
}

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

 

이번에는 기능도 클래스에 합쳐보자.

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

 

여기서 특이한 점은 여기서 만든 add() 메서드는 static 키워드를 사용하지 않는다.

 - 메서드는 객체를 생성해야 호출할 수 있다. 그런데 static이 붙으면 객체를 생성하지 않고도 메서드를 호출할 수 있다. static에 대한 자세한 내용은 뒤에서 알아보자.

인스턴스의 메서드를 호출하는 방법은 멤버 변수와 마찬가지로 .(dot)을 이용해서 접근한다.

 

정리하자면

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

5.  객체 지향 프로그래밍

앞에서 만들었던(제일 마지막 코드 제외 !) 코드들은 음악 플레이어의 데이터와 기능들이 분리되어 있었다. 이제 데이터와 기능을 묶어 음악 플레이어라는 개념을 온전히 하나의 클래스에 담아보자. 프로그램을 작성하는 절차도 중요하지만 음악 플레이어라는 개념을 객체로 온전히 만드는 것이 더 중요하다. 음악 플레이어라는 객체를 지향해보자.

 그러기 위해서는 프로그램의 실행 순서 보다는 음악 플레이어 클래스를 만드는 것 자체에 집중해야 한다. 음악 플레이어가 어떤 속성(데이터)을 가지고 어떤 기능(메서드)을 제공하는지 이 부분에 초점을 맞추어야 한다. 지금부터 우리는 음악 플레이어를 개발하는 개발자가 될 것이다. 이것을 어떻게 사용할지는 분리해서 생각하자. 쉽게 이야기해서 음악 플레이어를 만들어서 제공하는 개발자와 음악 플레이어를 사용하는 개발자가 분리되어 있다고 생각하면 된다.

 

음악 플레이어

  • 속성 : volume, isOn
  • 기능 : on(), off(), volumeUp(), volumeDown(), showStatus()
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() {
        if (isOn) {
            System.out.println("음악 플레이어 ON, 볼륨 : " + volume);
        } else {
            System.out.println("음악 플레이어 OFF");
        }
    }
}

MusicPlayer 클래스에 필요한 속성과 기능을 모두 정의했다.

이제 음악 플레이어가 필요한 곳에서 이 클래스만 있으면 온전한 음악 플레이어를 생성해서 사용할 수 있다.

public static void main(String[] args) {
    MusicPlayer musicPlayer = new MusicPlayer();
    //음악 플레이어 켜기
    musicPlayer.on();
    //볼륨 증가
    musicPlayer.volumeUp();
    //볼륨 증가
    musicPlayer.volumeUp();
    //볼륨 감소
    musicPlayer.volumeDown();
    //음악 플레이어 상태
    musicPlayer.showStatus();
    //음락 플레이어 끄기
    musicPlayer.off();
}
  • MusicPlayer를 사용하는 입장에서 MusicPlayer의 데이터인 volume, isOn 같은 데이터는 전혀 사용하지 않는다.
  • MuiscPlayer를 사용하는 입장에서 이제 MusicPlayer 내부에 어떤 속성(데이터)이 있는지 몰라도 되며 단순하게 제공하는 기능 중에 필요한 기능을 호출해서 사용하기만 하면 된다.

캡슐화

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

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