관리 메뉴

나구리의 개발공부기록

객체 지향 프로그래밍, 절차 지향 프로그래밍, 클래스와 메서드 본문

인프런 - 실전 자바 로드맵/실전 자바 - 기본편

객체 지향 프로그래밍, 절차 지향 프로그래밍, 클래스와 메서드

소소한나구리 2024. 12. 25. 11:56

출처 : 인프런 - 김영한의 실전 자바 - 기본편 (유료) / 김영한님  
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용


1. 절차 지향 프로그래밍

1) 시작

(1) 절차 지향 프로그래밍

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

(2) 객체 지향 프로그래밍

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

(3) 둘의 차이

  • 절차 지향은 데이터와 해당 데이터에 대한 처리 방식이 분리되어 있는 반면 객체 지향에서는 데이터와 그 데이터에 대한 행동(메서드)이 하나의 객체 안에 함께 포함되어 있음
  • 지금까지 자바 강의를 하며 작성한 프로그램은 절차 지향 프로그램이며 단순히 객체를 사용하기만 했다고 하여 객체 지향 프로그래밍이라고 할 수는 없음

(4-1) 음악 플레이어 만들기 요구사항

  • 음악 플레이어를 켜고 끌 수 있어야 함
  • 음악 플레이어의 볼륨을 증가, 감소할 수 있어야 함
  • 음악 플레이어의 상태를 확인할 수 있어야 함
  • 예시 출력
더보기
더보기

음악 플레이어를 시작합니다

음악 플레이어 볼륨:1

음악 플레이어 볼륨:2

음악 플레이어 볼륨:1

음악 플레이어 상태 확인

음악 플레이어 ON, 볼륨:1

음악 플레이어를 종료합니다

(4-2) 절차 지향 음악 플레이어1 - MusicPlayerMain1

  • oop1 패키지 생성 후 작성
  • 순서대로 프로그램이 작동하도록 단순하게 작성된 코드이며 이 코드를 점진적으로 변경할 예정
  • 시작해보면 예시 출력처럼 출력되는 것을 확인할 수 있음
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) 데이터 묶음

(1-1) 절차 지향 음악 플레이어2 - MusicPlayerData

  • MusicPlayerData라는 클래스를 만들고 음악 플레이어에 사용되는 데이터들을 여기에 묶어서 멤버 변수로 사용
  • volume과 isOn속성을 멤버 변수에 포함
package oop1;

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

 

(1-2) 절차 지향 음악 플레이어2 - MusicPlayerMain2

  • 음악 플레이어와 관련된 데이터는 MusicPlayerData 클래스에 존재하며 이 클래스를 사용하도록 기존 로직을 변경함
  • 이후에 프로그램 로직이 더 복잡해져서 다양한 변수들이 추가되더라도 음악 플레이어와 관련된 변수들은 MusicPlayerData data 객체에 속해있으므로 쉽게 구분할 수 있음
  • 마찬가지로 예시 출력과 동일하게 출력됨
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("음악 플레이어를 종료합니다");
    }
}

3) 메서드 추출

(1) 메서드를 사용하여 중복과 기능을 구분

  • 볼륨을 증가하고 감소시키는 부분의 코드가 중복되고 있음
  • 음악 플레이어 켜기 및 끄기, 볼륨 증가 및 감소, 음악 플레이어 상태 출력의 기능은 이후에 재사용 될 가능성이 높음

(2) 절차 지향 음악 플레이어3 - MusicPlayerMain3

  • 각각의 기능을 메서드로 만든 덕분에 모듈화가 되어 아래와 같은 장점이 생김
  • 중복 제거: 로직 중복이 제거되어 같은 로직이 필요하면 해당 메서드를 여러번 호출하면 됨
  • 변경 영향 범위: 기능을 수정할 때 해당 메서드 내부만 변경하면 됨
  • 메서드 이름 추가: 메서드 이름을 통해 코드를 더 쉽게 이해할 수 있음

** 모듈화

  • 쉽게 이야기하여 레고 블럭을 생각하면 되는데 필요한 블럭을 가져다 꼽아서 사용할 수 있음
  • 여기서 음악 플레이어의 기능이 필요하면 해당 기능을 메서드 호출만으로 손쉽게 사용할 수 있으며 음악 플레이어와 관련된 메서드를 조립하여 프로그램을 작성할 수 있음
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 showStatus(MusicPlayerData data) {
        System.out.println("음악 플레이어 상태 확인");
        if (data.isOn) {
            System.out.println("음악 플레이어 ON, 볼륨: " + data.volume);
        } else {
            System.out.println("음악 플레이어 OFF");
        }
    }

    static void volumeDown(MusicPlayerData data) {
        data.volume--;
        System.out.println("음악 플레이어 볼륨: " + data.volume);

    }
}

 

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

  • 클래스를 사용하여 관련된 데이터를 하나로 묶고 메서드를 사용해서 각각의 기능을 모듈화하여 상당히 깔끔하고 읽기 좋고 유지보수 하기 좋은 코드를 작성할 수 있었음
  • 그러나 지금 작성된 코드의 한계는 데이터와 기능이 분리되어 있다는 점인데 현재 음악 플레이어의 데이터는 MusicPlayerData에 있고 그 데이터를 사용하는 기능은 MusicPlayerMain3에 있는 각각의 메서드에 분리되어 있음
  • 그래서 음악 플레이어와 관련된 데이터는 MusicPlayerData를 사용해야하고 음악 플레이어와 관련된 기능은 MusicPlayerMain3의 각 메서드를 사용해야 함
  • 각각의 메서드를 보면 대부분 MusicPlayerData의 데이터를 사용하여 데이터와 그 데이터를 사용하는 기능이 매우 밀접하게 연관되어 있어 이후에 관련 데이터가 변경되면 MusicPlayerMain3 부분의 메서드들도 함께 변경해야하고 이렇게 데이터와 기능이 분리되어 있으면 유지보수 관점에서도 관리 포인트가 2곳으로 늘어남
  • 객체 지향 프로그래밍이 나오기 전까지는 지금과 같이 데이터와 기능이 분리되어 있어서 지금과 같은 코드가 최선이였으나 객체 지향 프로그래밍이 나오면서 데이터와 기능을 온전히 하나로 묶어 사용할 수 있게 되었음

2. 클래스와 메서드

1) 예제1

(1) ValueData

package oop1;

public class ValueData {
    int value;
}

 

(2) ValueDataMain

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
*/

 

(3) 설명

  • ValueData라는 인스턴스를 생성하고 외부에서 ValueData.value에 접근해 숫자를 하나씩 증가시키는 단순한 코드이며 데이터인 value와 value의 값을 증가시키는 기능인 add() 메서드가 서로 분리되어 있음
  • 자바 같은 객체 지향 언어는 클래스 내부에 속성(Data)과 기능(메서드)을 함께 포함할 수 있어 클래스 내부에 멤버 변수 뿐만 아니라 메서드도 함께 포함할 수 있음

2) 예제2

(1) ValueObject

  • 숫자를 증가시키는 기능도 클래스에 함께 포함하여 새로운 클래스를 정의
  • 이 클래스에는 데이터인 value와 해당 데이터를 사용하는 기능인 add() 메서드를 함께 정의되었음

** 참고

  • 여기서 만드는 add()메서드에는 static 키워드를 사용하지 않음
  • 메서드는 객체를 생성해야 호출할 수 있는데 static이 붙으면 객체를 생성하지 않고도 메서드를 호출할 수 있음
  • static에 대한 자세한 내용은 뒤에서 설명함
package oop1;

public class ValueObject {
    int value;

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

 

(2) ValueObjectMain

  • ValueObject valueObject = new ValueObject(): valueObject라는 객체를 생성하면 이 객체는 멤버 변수 뿐만 아니라 내부에 기능을 수행하는 add() 메서드도 함께 존재함
  • 인스턴스의 메서드를 호출하는 방법은 멤버 변수를 사용하는 방법과 동일하며 .(dot)을 찍어서 객체에 접근한 다음 원하는 메서드를 호출하면 됨
  • add() 메서드를 호출하면 메서드 내부에서 value++을 호출하게 되며 이때 value에 접근해야 하는데 기본적으로 본인 인스턴스에 있는 멤버 변수에 접근하며 본인 인스턴스가 객체 생성시 참조한 참조값을 사용하므로 자기자신인 value에 접근하게 됨
  • ++ 연산으로 value의 값을 하나 증가시킴
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
*/

 

(3) 정리

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

3. 객체 지향 프로그래밍

1) 객체 지향 음악 플레이어 개발

(1) 음악 플레이어

  • 지금까지 개발한 음악 플레이어는 데이터와 기능이 분리되어 있었는데 이제 데이터와 기능을 하나로 묶어서 음악 플레이어라는 개념을 온전히 하나의 클래스에 담아서 개발
  • 프로그램을 작성하는 절차도 중요하지만 지금은 음악 플레이어라는 개념을 객체로 온전히 만드는 것이 더 중요하므로 음악 플레이어라는 객체를 지향하는 것을 우선으로 개발을 진행
  • 그러기 위해서는 프로그램의 실행 순서 보다는 음악 플레이어 클래스를 만드는 것 자체에 집중해야 하며 음악 플레이어가 어떤 속성(데이터)을 가지고 어떤 기능(메서드)을 제공하는지 이 부분에 초점을 맞추어야 함
  • 쉽게 이야기하면 음악 플레이어를 만들어서 제공하는 개발자와 음악 플레이어를 사용하는 개발자가 분리되어 있다고 생각하면 됨

(2) 음악 플레이어의 속성과 기능

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

(3) MusicPlayer

  • 음악 플레이어에 필요한 속성과 기능을 모두 정의하여 음악 플레이어가 필요한 곳에서 이 클래스만 있으면 온전한 음악 플레이어를 생성해서 사용할 수 있음
  • 즉 음악 플레이어를 사용하는데 필요한 모든 속성과 기능이 하나의 클래스에 포함되어 있음
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 volumeDown() {
        volume--;
        System.out.println("음악 플레이어 볼륨:" + volume);
    }

    void volumeUp() {
        volume++;
        System.out.println("음악 플레이어 볼륨:" + volume);
    }

    void showStatus() {
        System.out.println("음악 플레이어 상태 확인");
        if (isOn) {
            System.out.println("음악 플레이어 On, 볼륨: " + volume);
        } else {
            System.out.println("음악 플레이어 Off");
        }
    }
}

 

(4) MusicPlayerMain4 - 뮤직 플레이어 사용

  • 뮤직 플레이어를 사용하기위해 필요한 모든 것은 MusicPlayer안에 들어있어 MusicPlayer 객체를 생성하고 필요한 기능을 호출하기만 하면 됨
  • MusicPlayer를 사용하는 입장에서는 MusicPlayer의 데이터인 volume, isOn 같은 데이터는 전혀 사용하지 않음
  • MusicPlayer를 사용하는 입장에서는 단순하게 MusicPlayer가 제공하는 기능 중에 필요한 기능을 호출해서 사용하기만 하면됨
package oop1;

public class MusicPlayerMain4 {
    public static void main(String[] args) {
        MusicPlayer musicPlayer = new MusicPlayer();

        // 음악 플레이어 켜기
        musicPlayer.on();

        // 볼륨 증가
        musicPlayer.volumeUp();

        // 볼륨 증가
        musicPlayer.volumeUp();

        // 볼륨 감소
        musicPlayer.volumeDown();

        // 음악 플레이어 상태
        musicPlayer.showStatus();

        // 음악 플레이어 끄기
        musicPlayer.off();
    }
}
/* 실행 결과
음악 플레이어를 시작합니다
음악 플레이어 볼륨:1
음악 플레이어 볼륨:2
음악 플레이어 볼륨:1
음악 플레이어 상태 확인
음악 플레이어 On, 볼륨: 1
음악 플레이어를 종료합니다
*/

 

(5) 캡슐화

  • MusicPlayer를 보면 음악 플레이어를 구성하기 위한 속성과 기능이 마치 하나의 캡슐에 쌓여있는 것 같이 되어있음
  • 이렇게 속성과 기능을 하나로 묶어서 필요한 기능을 메서드를 통해 외부에 제공하는 것을 캡슐화라고 함
  • 객체 지향 프로그래밍 덕분에 음악 플레이어 객체를 사용하는 입장에서 진짜 음악 플레이어를 만들고 사용하는 것처럼 친숙하게 느껴져 코드가 더 읽기 쉬운 것은 물론이고 속성과 기능이 한 곳에 있기 때문에 변경도 쉬워짐
  • 예를 들어 MusicPlayer 내부의 volume이라는 필드 이름이 다른 이름으로 변한다고 할 때 MusicPlayer 내부만 변경하면 되며, 내부에서 출력하는 메시지를 변경할 때도 MusicPlayer 내부만 변경하면 되고 다른 코드는 변경하지 않아도됨
  • 즉, MusicPlayer를 사용하는 개발자는 코드를 전혀 변경하지 않아도 됨
  • 물론 외부에서 호출하는 MusicPlayer의 메서드 이름을 변경하면 MusicPlayer를 사용하는 곳의 코드도 변경해야함

** 참고

  • 객체지향과 캡슐화에 대한 개념은 이것이 전부가 아니라 더 많은 내용들이 있으며 수업을 진행하면서 하나씩 추가적인 개념을 배움

4. 문제와 풀이

1) 절차 지향 직사각형 프로그램을 객체 지향으로 변경

(1) 요구사항

  • 절차 지향 프로그래밍 방식으로 되어 있는 코드를 객체 지향 프로그래밍 방식으로 변경
  • Rectangle 클래스를 생성
  • RectangleOopMain에 Rectangle클래스를 사용하는 main() 코드를 작성

(2) 절차 지향 코드

더보기
더보기
package oop1.ex;

public class RectangleProceduralMain {
    public static void main(String[] args) {
        int width = 5;
        int height = 8;
        int area = calculateArea(width, height);
        System.out.println("넓이: " + area);

        int perimeter = calculatePerimeter(width, height);
        System.out.println("둘레 길이: " + perimeter);

        boolean square = isSquare(width, height);
        System.out.println("정사각형 여부: " + square);
    }

    static int calculateArea(int width, int height) {
        return width * height;
    }

    static int calculatePerimeter(int width, int height) {
        return 2 * (width + height);
    }

    static boolean isSquare(int width, int height) {
        return width == height;
    }
}

 

넓이: 40
둘레 길이: 26
정사각형 여부: false

 

(3) 정답

더보기
더보기
package oop1.ex;

public class RectangleOopMain {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.width = 5;
        rectangle.height = 8;

        int area = rectangle.calculateArea();
        System.out.println("넓이: " + area);

        int perimeter = rectangle.calculatePerimeter();
        System.out.println("둘레 길이: " + perimeter);

        boolean square = rectangle.isSquare();
        System.out.println("정사각형 여부: " + square);
    }
}

 

출력 결과는 동일

2) 객체 지향 계좌

(1) 요구사항

  • Account 클래스 생성
    - int balance 잔액
    - deposit(int amount): 입금 메서드, 입금시 잔액이 증가
    - withdraw(int amount): 출금 메서드, 출금시 잔액이 감소하며 잔액이 부족하면 잔액 부족을 출력
  • AccountMain 클래스를 생성 후 main() 메서드를 통해 프로그램을 시작
    - 계좌에 10000원을 입금
    - 계좌에서 9000원을 출금
    - 계좌에서 2000원을 출금을 시도하여 잔액 부족 출력을 확인
    - 잔고를 출력, 잔고: 1000

(2) 실행 결과

잔액 부족
잔고: 1000

 

(3) 정답

더보기
더보기
package oop1.ex;

public class Account {
    int balance;

    void deposit(int amount) {
        balance += amount;
    }

    void withdraw(int amount) {
        if (balance < amount) {
            System.out.println("잔액 부족");
        } else {
            balance -= amount;
        }
    }
}
package oop1.ex;

public class AccountMain {
    public static void main(String[] args) {
        Account account = new Account();
        account.deposit(10000);
        account.withdraw(9000);
        account.withdraw(2000);
        System.out.println("잔고: " + account.balance);
    }
}

 

잔액 부족
잔고: 1000


5. 정리

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

(1)개념

  • 객체 지향 프로그래밍과 절차 지향 프로그래밍은 서로 대치되는 개념이 아니라 객체 지향이라도 프로그램의 작동 순서는 중요함
  • 다만 어디에 더 초점을 맞추는가에 둘의 차이가 있는것이며 객체 지향의 경우 객체의 관계를 중시하는 반면 절차 지향의 경우 데이터와 기능이 분리되어 있고, 프로그램이 어떻게 작동하는지 그 순서에 초점을 맞춤

(2) 절차 지향 프로그래밍

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

(3) 객체 지향 프로그래밍

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

(4) 둘의 중요한 차이

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

2) 객체란?

(1) 개념

  • 세상의 모든 사물을 단순하게 추상화해보면 속성과 기능 두가지로 설명할 수 있음
  • 객체 지향 프로그래밍은 모든 사물을 속성과 기능을 가진 객체로 생각하는 것이며 객체에는 속성과 기능만 존재함
  • 이렇게 단순화 하면 세상에 있는 객체들을 컴퓨터 프로그램으로 쉽게 설계할 수 있어 이런 장점들 덕분에 지금은 객체 지향 프로그래밍이 가장 많이 사용됨
  • 참고로 실세계와 객체가 항상 1:1로 매칭이 되는 것은 아니므로 프로그램으로 가져오면 거기에 맞게 변형을 해주어야 함
  • 객체 지향의 특징은 속성과 기능을 하나로 묶는 것 뿐만 아니라 캡슐화, 상속, 다형성, 추상화, 메시지 전달 같은 다양한 특징들이 있음

(2) 자동차

  • 속성: 차량 색상, 현재 속도 등
  • 기능: 엑셀, 브레이크, 문 열기, 문 닫기 등등

(3) 동물

  • 속성: 색상, 키, 온도 등
  • 기능: 먹는다, 걷는다 등

(4) 게임 캐릭터

  • 속성: 레벨, 경험치, 소유한 아이템들 등
  • 기능: 이동, 공격, 아이템 기능