Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
JAVA/Java Intermediate(OOP)

3. 절차 지향 vs 객체 지향(OOP)

by Toughie 2024. 2. 23.

 

절차 지향 프로그래밍

말 그대로 '절차'를 지향하는 프로그래밍 방식이다. 즉 '실행 순서'가 중요하다.

어떤 값을 얻기 위해서 '어떻게' 코드를 실행해야 하는가?

처리할 데이터와 처리 방법이 분리되어 있다.

객체 지향 프로그래밍

세상의 대부분의 것들은 특정한 역할이나 책임이 있는 객체로 표현될 수 있다.

객체 지향 프로그래밍에서는 이런 기능은 네가 하고 저런 기능은 내가 하고 와 같이 객체들 사이에서 상호작용(메세징)이 일어난다.

객체 지향에서는 '관련도'가 높은 데이터와 해당 데이터를 처리하는 메서드를 함께 묶어서 관리하게 된다.

 

아래 예시 코드를 통해서 절차 지향적인 프로그램을 객체 지향적 프로그램으로 변경해 보자.

스마트 전등을 크고 끼는 시나리오를 생각해 보자. 

기존 예시 코드들과 같이 메인 메서드 안에서 그냥 전부 다 하드코딩 되어 있다.

 

public class LampControlerMain {
    public static void main(String[] args) {
        int brightness = 0;
        boolean isOn = false;

        // 조명 키기
        isOn = true;
        System.out.println("조명이 켜졌습니다.\n");
        // 밝기 증가
        brightness += 1;
        System.out.printf("조명 밝기 : %d\n",brightness);
        // 밝기 증가
        brightness++;
        System.out.printf("조명 밝기 : %d\n",brightness);
        // 밝기 감소
        brightness--;
        System.out.printf("조명 밝기 : %d\n",brightness);
        // 전원 체크
        if (isOn) {
            System.out.printf("조명이 켜져있으며 밝기는 %d 칸델라 입니다.",brightness);
        } else {
            System.out.println("조명이 꺼져있습니다.");
        }
        // 전원 종료
        isOn = false;
        System.out.println("조명이 꺼졌습니다.");
    }
}

 

흠.. 뭐 간단한 프로그램이라면 괜찮겠지만 전구의 기능이 추가될 수도 있고 변수가 촤르르 나열되어 있는 것이 별로니

멤버 변수만 따로 분리해 보도록 하자. 

이제 램프에 관련된 데이터는 전부 LampData를 살펴보면 될 것이다. (추가, 삭제, 확인 용이)

 

public class LampData {
    int brightness;
    boolean isOn;
}

 

초기값을 안 준 이유는, 어차피 초기화될 때 기본 값으로 초기화되기 때문이다. (int는 0, boolean은 false)

오호.. 근데 이제 클래스 인스턴스를 생성할 테니 힙 영역에 있는 LampData 인스턴스를 참조하는 방식으로 가야겠구나?
기존에는 지역 변수(brightness, isOn)로 스택 프레임 안에서 놀았으니 말이다.

 

public class LampControlerMain {
    public static void main(String[] args) {

        LampData data = new LampData();

        data.isOn = true;
        System.out.println("조명이 켜졌습니다.\n");
        data.brightness += 1;
        System.out.printf("조명 밝기 : %d\n",data.brightness);
        data.brightness++;
        System.out.printf("조명 밝기 : %d\n",data.brightness);
        data.brightness--;
        System.out.printf("조명 밝기 : %d\n",data.brightness);
        if (data.isOn) {
            System.out.printf("조명이 켜져있으며 밝기는 %d 칸델라 입니다.",data.brightness);
        } else {
            System.out.println("조명이 꺼져있습니다.");
        }
        data.isOn = false;
        System.out.println("조명이 꺼졌습니다.");
    }
}

 

점문법을 통해 data 인스턴스의 멤버에 접근해서 기존과 동일하게 동작한다. (메모리 사용 방법은 달라졌지만)

하지만 조명의 밝기를 올리고 내리고 비슷한 동작이 많아서 더 깔끔하게 만들 수 있을 것 같다.

중복되는 동작이 있을 때 우리는 이것을 함수로 만들 수 있으니까!

또 데이터 조작과 출력을 하나로 묶는 효과도 있을 것이다. 

 

public class LampControlerMain3 {

    public static void main(String[] args) {

        LampData data = new LampData();

        turnOn(data);
        brighter(data);
        brighter(data);
        darker(data);
        checkStatus(data);
        turnOff(data);
    }

    static void turnOn(LampData data) {
        data.isOn = true;
        System.out.println("조명이 켜졌습니다.\n");

    }
    static void turnOff(LampData data) {
        data.isOn = false;
        System.out.println("조명이 꺼졌습니다.");
        
    }
    static void brighter(LampData data) {
        data.brightness++;
        System.out.printf("조명 밝기 : %d\n",data.brightness);
    }
    static void darker(LampData data) {
        data.brightness--;
        System.out.printf("조명 밝기 : %d\n",data.brightness);
    }
    static void checkStatus(LampData data) {
        if (data.isOn) {
            System.out.printf("조명이 켜져있으며 밝기는 %d 칸델라 입니다.",data.brightness);
        } else {
            System.out.println("조명이 꺼져있습니다.");
        }
    }
}

 

메인 메서드에서 로직을 함수로 추출(모듈화)해서 좀 더 깔끔하게 만들어 보았다.

(인스턴스 메서드가 아니기 때문에 static으로 선언함)

 

static으로 선언된 메서드들은 클래스의 로딩 시점에 메모리의 "메서드 영역" 또는 "코드 영역"에 저장된다.
이 영역은 JVM 내부에서 클래스의 바이트코드를 저장하는 영역으로,
클래스 로딩 시점에 초기화되고 프로그램의 수명 동안 유지된다.

 

우선 중복이 줄어들었고 밝기 조절을 더 해야 하는 경우에도 함수만 부르면 돼서 편해졌다.

 

또한 로직 수정이 필요할 때 메인 메서드 안에 로직이 다 있던 경우에는 다른 로직에도 영향이 갈 수 있었는데(사이드 이펙트 위험)

지금은 각 함수별로 수정을 할 수 있어서 더욱 안전하다. (스코프의 개념이 적용되니)

 

또 (함수명을 제대로 짓는다면) 호출만으로도 어떤 동작을 하는지 유추할 수 있어 가독성이 좋아졌다. 

하지만 모든 메서드에 data를 전달해 주는 모습이 좀 아쉽다. 결국 data와 메서드가 너무 밀접하게 연관되어 있다는 것이다.

 

흠.. 근데 반대로 생각하면 만약 전등에 문제가 생겼을 때 처음 코드는 한 파일만 보면 됐는데

지금은 LampData 클래스도 살펴봐야 하고 LampControlerMain 클래스도 살펴봐야 한다.

즉 데이터와 기능이 아주 밀접한데도 불구하고 아예 분리되어 있다는 점이다.

LampControlerMain 클래스는 LampData가 없으면 아무것도 하지 못한다. (아주 의존적이다.)

 

이와 같이 데이터와 기능이 분리되어 있는 것이 절차 지향 프로그래밍의 최선이었다. 

어떻게 보면 데이터 분리 잘했고 해당 데이터 제어 함수 잘 분리했으니 객체지향적인 거 아닌가 할 수 있겠지만,

결국 우리는 자기 데이터를 스스로 조작할 수 있는 조명관리의 역할을 하는 객체를 만들어야 한다.

이것이 진정한 객체가 될 테니.. 데이터와 기능을 하나로 완전 묶어보자.

(cf. 구조체는 메서드를 포함하지 못한다는 점! 물론 자바에는 없지만..

메서드가 포함된다는 것이 클래스의 장점이자, 상호작용의 수단이다.)

 

public class SmartLamp {
    int brightness;
    boolean isOn;
    
    void turnOn() {
        //this.isOn = true;
        isOn = on;
        System.out.println("조명이 켜졌습니다.\n");
    }
    void turnOff() {
        isOn = false;
        System.out.println("조명이 꺼졌습니다.\n");
    }
    void brighter() {
        brightness++;
        System.out.printf("조명 밝기 : %d\n",brightness);
    }
    void darker() {
        brightness--;
        System.out.printf("조명 밝기 : %d\n",brightness);
    }
    void checkStatus() {
        if (isOn) {
            System.out.printf("조명이 켜져있으며 밝기는 %d 칸델라 입니다.\n\n",brightness);
        } else {
            System.out.println("조명이 꺼져있습니다.");
        }
    }
}

 

*this (멤버 변수에 대한 조작을 가하기 때문에 this라는 키워드를 사용한다. '이 인스턴스의' _ swift의 self와 같다고 볼 수 있겠다.)

물론 생략 가능하다. 

 

이제 조명에 대한 설계도를 만들었다.(클래스) 

조명이 필요하면 이 설계도 대로 여러 개를 만들 수도 있고, 해당 조명을 직접 조작할 수 있다.(내부 메서드를 통해서)

또한 진짜 조명의 버튼을 누르는 것처럼 내부적으로 어떻게 동작하는지 몰라도 그냥 필요한 기능만 사용할 수 있다.(추상화)

 

public class MyProgram {
    public static void main(String[] args) {
        SmartLamp myLamp1 = new SmartLamp();
        SmartLamp myLamp2 = new SmartLamp();
        
        myLamp1.turnOn();
        myLamp1.brighter();
        myLamp1.darker();
        myLamp1.checkStatus();
        myLamp1.turnOff();
        
        myLamp2.turnOn();
        myLamp2.turnOff();
    }
}

 

데이터와 기능이 SmartLamp 안에 쏙 들어가 있고, 필요한 기능은 '메서드를 통해서 외부에 제공'하기에

이를 캡슐화(encapsulation) 되어 있다고 한다.

램프가 고장 날 경우 SmartLamp 클래스로 가서 수정하면 될 것이다.


 

'정말 간단한' 수준으로 객체 지향 프로그래밍에 대한 맛을 봤다.

핵심은 관련된 속성(데이터)과 기능(메서드)을 객체(자바에서는 클래스라고 표현해도 될 듯?)에 묶어서 사용하는 것이다. 

객체를 잘 만드는 것이 중요하다. 프로그램이 커지면 여러 객체들이 서로 소통을 할 것이기 때문이다. 

(소통을 하지 않는다면 독립적으로 자신의 역할을 충실히 수행해야 할 것이다.)

 

객체 지향의 5대 특징에 대해 말하시오~라고 하면

캡상추다메 이런 식으로 많이들 외우는데 (캡슐화, 상속, 추상화, 다형성, 메시지)

이번에는 캡슐화와 추상화 정도? 찍먹 해본 수준 정도라 생각한다. 나머지 특징들은 이후에 좀 더 알아보도록 한다.

 

개념적으로는 이렇지만, 정말 객체를 잘 설계하는 능력은

실무 등에서 큰 프로젝트의 객체들을 살펴보거나 직접 짜보면서 길러지지 않을까 싶다.


학습 출처

김영한의 실전 자바 - 기본편

'JAVA > Java Intermediate(OOP)' 카테고리의 다른 글

5. 접근 제어자와 캡슐화  (0) 2024.03.03
4. 생성자 Construct  (0) 2024.03.02
2. 기본형과 참조형 (Primitive, Reference), NullPointerException  (0) 2024.02.12
1. 클래스 (Class)  (0) 2024.02.10
0. OOP의 세계로  (0) 2024.02.10