절차 지향 프로그래밍1 - 시작

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

 

절차 지향 프로그래밍

  • 실행 순서를 중요하게 생각하는 방식
  • 프로그램의 흐름을 순차적으로 따르며 처리하는 방식
  • "어떻게"를 중심으로 프로그래밍

객체 지향 프로그래밍

  • 객체를 중요하게 생각하는 방식
  • 실제 세계의 사물이나 사건을 객체로 보고, 이러한 객체들 간의 상호작용을 중심으로 프로그래밍하는 방식
  • "무엇을"을 중심으로 프로그래밍

 

문제: 음악 플레이어 만들기

요구 사항:

  1. 음악 플레이어를 켜고 끌 수 있어야 한다.
  2. 음악 플레이어의 볼륨을 증가, 감소할 수 있어야 한다.
  3. 음악 플레이어의 상태를 확인할 수 있어야 한다.

예시 출력:

 

절차 지향 음악 플레이어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("음악 플레이어를 종료합니다.");
    }
}

실행 결과

순서대로 프로그램이 작동하도록 단순하게 작성했다. 이 코드를 점진적으로 변경해보자.

 


절차 지향 프로그래밍2 - 데이터 묶음

앞서 작성한 코드에 클래스를 도입하자. MusicPlayerData라는 클래스를 만들고, 음악 플레이어에 사용되는 데이터들을 여기에 묶어서 멤버 변수로 사용하자.

 

절차 지향 음악 플레이어2 - 데이터 묶음

package oop1;

public class MusicPlayerData {
    int volume = 0;
    boolean isOn = false;
}

음악 플레이어에 사용되는 volume, isOn 속성을 MusicPlayerData의 멤버 변수에 포함했다.

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 클래스에 존재한다. 음악 플레이어와 관련된 변수들이 MusicPlayerData data 객체에 속해있기 때문에 코드가 복잡해져서 다양한 변수들이 추가되어도 쉽게 구분할 수 있게 된다.

 

절차 지향 프로그래밍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");
        }
    }
}

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

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

레고 블럭처럼 필요한 블럭을 가져다 꼽아서 사용할 수 있다.

음악 플레이어의 기능이 필요하면 해당 기능을 메서드 호출만으로 손쉽게 사용할 수 있다. (각각의 기능을 모듈화함)

 

절차 지향 프로그래밍의 한계: 데이터와 기능이 분리되어 있다

 

음악 플레이어의 데이터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와 value의 값을 증가시키는 기능인 add( ) 메서드가 서로 분리되어 있다.

 

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

 

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

package oop1;

public class ValueObject {
    int value;

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

 

참고: 클래스에서 만드는 메서드에는 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);
    }
}

실행 결과

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

 

1. 인스턴스 생성

2. 인스턴스의 메서드 호출

인스턴스의 메서드를 호출하는 방법은 멤버 변수를 사용하는 방법과 동일하게 . (dot)을 찍어서 객체에 접근하여 원하는 메서드를 호출하는 것이다.

valueObject.add(); //1
x002.add(); //2: x002 ValueObject 인스턴스에 있는 add() 메서드를 호출한다.

 

3. add( ) 메서드 내부에서 value++을 호출

value에 접근해야 하는데, 기본적으로 본인 인스턴스에 있는 멤버 변수에 접근한다. 본인 인스턴스가 x002 참조값을 사용하므로 자기 자신인 x002.value에 접근하게 된다.

 

4. ++ 연산으로 value의 값을 하나 증가시킴

 

 

[정리]

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

 

 


객체 지향 프로그래밍

이제 데이터와 기능을 하나로 묶어서 음악플레이어 프로그램을 객체 지향 프로그래밍으로 만들어보자.

 

음악 플레이어

  • 속성: volume, isOn
  • 기능: on( ), off( ), volumeUp( ), volumeDown( ), showStatus( )

 

객체 지향 음악 플레이어

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

MusicPlayer 클래스에 음악 플레이어에 필요한 속성과 기능을 모두 담았다. 이제 음악 플레이어가 필요한 곳에서 이 클래스만 있으면 온전한 음악 플레이어를 생성해서 사용할 수 있다.

 

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

실행 결과

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

 

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

캡슐화(Encapsulation)

"속성과 기능이 하나의 캡슐에 쌓여있다."

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

+ Recent posts