본문 바로가기
✏️Java 공부/Java의 정석

[Java 공부/Java의 정석] Chapter.06 : 객체지향 프로그래밍 1 - 1 (객체지향언어, 클래스와 객체)

by 코코의 주인 2022. 6. 30.

객체지향언어의 역사

 객체지향 이론은 '실제 세계는 사물(객체)로 이루어져 있으며, 발생하는 모든 사건들은 사물 간의 상호작용이다.'라는 기본 개념을 가진다. 실제 사물의 속성과 기능을 분석한 다음, 데이터(변수)와 함수로 정의함으로써 실제 세계를 컴퓨터 속에 옮겨 놓는다는 이론이다.

 객체지향 이론이 발달하면서 상속, 캡슐화, 추상화 개념이 생겨났고, 이들이 점차 구체적으로 발전하면서 객체지향 이론을 프로그래밍 언어에 적용한 객체지향 언어가 탄생하게 되었다.


객체지향 언어란?

 객체지향 언어는 기존의 프로그래밍 언어에 몇 가지 새로운 규칙을 추가한 보다 발전된 형태의 언어라 할 수 있다. 이러한 규칙들은 코드들 간에 서로 관계를 맺어 줌으로써 보다 유기적으로 프로그램을 구성하는 것을 가능하게 한다.

 객체지향 언어는 아래와 같은 대표적인 특징을 가진다.

1. 코드의 재사용성이 높다.
- 새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다.

2. 코드의 관리가 용이하다.
- 코드 간의 관계를 이용해서 쉽게 코드를 변경할 수 있다.

3. 신뢰성이 높은 프로그래밍을 가능하게 한다.
- 제어자와 메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하도록 할 수 있다. 코드의 중복을 제거하여 코드 불일치로 인한 오작동을 방지할 수 있다.

한 줄 요약을 하자면 '객체지향 언어는 코드의 재사용성이 높고 유지 보수가 용이하다'라고 할 수 있겠다.


클래스와 객체

 객체지향 프로그래밍에서는 클래스와 객체에 대해 잘 알아두어야 한다.

클래스
- 객체를 정의해 놓은 것
- 객체를 생성하는 데 사용함

객체
- 클래스의 정의에 의해 생성된 것 
- 프로그래밍 언어에서는 클래스에 의해 생성된 것이 메모리에 올라간 것을 객체라 함

 저걸 읽으면 '이게 무슨 말장난인가'라는 느낌이 들 것이다. 때문에 클래스와 객체는 예제를 통해 이해하는 것이 편하다. 현실 세계에서 클래스와 객체의 예를 들 때 설계도와 제품의 예를 많이 든다. 혹자는 이게 잘못된 예시라고 하는 의견도 있던데, 일단 이 글에서는 설계도와 제품의 예로 설명하겠다.

붕어빵 예시

 클래스를 설명할 때 가장 많이 쓰는 예시로 붕어빵이 있다. 길에서 붕어빵을 파는 모습을 본 적이 있을 것이다.

 붕어빵 장수가 붕어빵 기계를 이용해서 붕어빵을 만드는 과정은 다음과 같다.

1. 붕어빵 틀을 연다
2. 붕어빵 틀에 반죽을 넣는다.
3. 반죽 위에 앙금을 올린다.
4. 앙금 위에 다시 반죽을 올린다.
5. 붕어빵 틀을 닫고 굽는다. 
6. 붕어빵을 뒤집어서 반대쪽 면을 굽는다.
7. 완성된 붕어빵을 틀에서 뺀다

 이러한 방법으로 붕어빵 장수는 붕어빵을 대량 생산할 수 있다.

 이때, 붕어빵을 찍어내는 틀을 클래스,

 붕어빵 틀에서 찍어낸 붕어빵을 객체라 할 수 있다.


객체와 인스턴스

클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화(instantiate)라 하며,

어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스(instance)라 한다.

 

 예를 들어, 붕어빵 클래스에서 붕어빵 한 개를 찍어냈다고 하자. 이때, 만들어진 붕어빵을 붕어빵 클래스의 인스턴스라고 한다. 이렇게 되면 '결국 객체와 인스턴스가 같은 말이 아니냐?'라는 생각이 들 수 있다. 인스턴스와 객체는 의미가 같은 것이 맞다. 하지만 문맥에 따라 구별하여 좀 더 자연스러운 표현을 사용하기 때문에 알아둘 필요가 있다.

EX)
붕어빵은 인스턴스다 -> 붕어빵은 객체다.
붕어빵은 붕어빵 클래스의 객체다. -> 붕어빵은 붕어빵 클래스의 인스턴스다.

 

객체의 구성요소 - 속성과 기능

 객체의 구성 요소로는 속성과 기능이 있다. 속성과 기능을 뜻하는 다른 용어들이 다양하기 때문에 아래에 정리해뒀다. 밑줄 친 표현은 "자바의 정석" 책에서 주로 사용하는 표현이다. 이 글에서도 주로 저 표현들을 사용할 거 같다.

속성(property)의 다른 표현 :  멤버 변수(member variable), 특성(attribute), 필드(field), 상태(state)
기능(function)의 다른 표현 : 메서드(method), 함수(function), 행위(behavior)

예시)

 TV를 예로 들어 설명하자면 TV에는 외형을 결정하는 크기, 길이, 높이, 색상 등의 변수가 있고, 상태를 표현하는 채널, 볼륨 등의 속성이 필요하다. 필요한 기능으로는 전원 켜기, 전원 끄기, 볼륨 조절, 채널 변경 등이 있다.

 이를 TV 클래스로 나타내면 아래와 같다.

class  TV {

    String color;  //색상
    boolean power;  //전원 상태
    int channel;  //채널
    int volume;  //음량
    
    void power() {  //전원을 켜고 끄는 메서드
    	power = !power;
    }
        
    void channelUp() {  //채널 올리는 메서드
    	channel++;
    }
    
    void channelDown() {  //채널 내리는 메서드
    	channel--;
    }
    
    void volumeUp() {  //볼륨 올리는 메서드
    	volume++;
    }
    
    void volumeDown() {  //볼륨 내리는 메서드
    	volume--;
    }
}

 속성은 클래스 내에서 변수로 나타내고, 기능은 함수로 나타낸다. 이렇게 나타낸 것을 멤버 변수와 메서드라고 한다.

멤버 변수
- 멤버 변수는 클래스 안에 생성된 변수를 뜻한다. 맴버변수는 클래스 안에 있는 모든 메서드에 접근이 가능하다.

메서드
- 메서드는 멤버 변수를 사용하여 클래스의 기능을 구현한 함수를 뜻한다.

인스턴스의 생성과 사용

클래스로부터 인스턴스를 생성하기 위한 방법은 아래와 같다. 아래의 코드는 TV 클래스에서 인스턴스를 생성하는 코드다.

클래스명 변수명;  //클래스의 객체를 참조하기 위한 참조변수를 선언
변수명 = new 클래스명();  //클래스의 객체를 생성 후, 객체의 주소를 참조변수에 저장

TV t;  //TV 클래스 타입의 참조변수 t를 선언
t = new TV();  //TV 인스턴스를 생성한 후, 생성된 TV 인스턴스의 주소를 t에 저장

아래 예제 코드는 TV 클래스로부터 인스턴스를 생성하고 인스턴스의 속성(channel)과 메서드(channelDown())을 사용하는 방법을 보여주는 것이다.

class TV {

    String color;  //색상
    boolean power;  //전원 상태
    int channel;  //채널
    int volume;  //음량

    void power() {  //전원을 켜고 끄는 메서드
        power = !power;
    }

    void channelUp() {  //채널 올리는 메서드
        channel++;
    }

    void channelDown() {  //채널 내리는 메서드
        channel--;
    }

    void volumeUp() {  //볼륨 올리는 메서드
        volume++;
    }

    void volumeDown() {  //볼륨 내리는 메서드
        volume--;
    }
}

public class TvTest {
    public static void main(String[] args) {
        TV tv1 = new TV();

        tv1.channel = 7;    //tv1의 채널을 7로 변경
        System.out.println("tv1의 채널은 " + tv1.channel + " 번입니다.");    //tv1의 해널은 : 7 번입니다.
        
        tv1.channelDown();	//tv1의 채널 내리기
        System.out.println("tv1의 채널은 " + tv1.channel + " 번입니다.");   //tv1의 해널은 : 6 번입니다.
    }
}

 인스턴스와 참조 변수의 관계는 마치 TV와  TV 리모컨의 관계와 같다. TV 리모컨(참조 변수)을 사용하여 TV(인스턴스)를 다루기 때문이다. 또한 에어컨 리모컨으로 TV를 조작할 수 없듯, TV 인스턴스를 사용하려면  TV 클래스 타입의 참조 변수가 필요하다.

인스턴스는 참조 변수를 통해서만 다룰 수 있으며, 참조 변수의 타입은 인스턴스의 타입과 일치해야 한다.

클래스의 또 다른 정의

 클래스는 데이터와 함수의 결합(구조체 + 함수) 형태로, 객체지향 프로그래밍에서 클래스를 사용하면 변수와 함수가 서로 유기적으로 연결되어 작업이 간단하고 명료해지는 장점이 있다.

 객체지향적 코드가 비 객체지향적 코드에 비해 얼마나 효율적인지를 알아보기 위해 예시를 들어보도록 하겠다. 3개의 시간 데이터를 관리하는 코드를 비객체지향적인 방법과 객체지향적인 방법으로 작성한 내용이다.

 

비객체지향적 코드

 비객체지향적 코드로 3개의 시간을 다뤄야 한다면 아래와 같이 해야 할 것이다. 이는 다뤄야 하는 시간이 늘어날 때마다 시, 분, 초를 위한 변수를 추가해줘야 하는 불편함이 있다.

int hour1, hour2, hour3;
int minute1, minute2, minute3;
float second1, second2, second3;


객체지향적 코드

이제 객체지향적 코드로 다시 작성해보자. 우선 시간을 저장하기 위한 Time 클래스를 정의한다.

class Time {
	int hour;
    int minute;
    float seond;
}

그리고 만들어진 클래스로 3개의 객체를 생성하면 끝난다.

Time t1 = new Time();
Time t2 = new Time();
Time t3 = new Time();

다뤄야 하는 시간 데이터의 개수가 늘어나더라도 변수를 매번 새로 선언해줘야 하는 불편함과 복잡함이 사라진 것을 볼 수 있다. 또한 관리도 매우 간단해졌다.

 

데이터의 유효성 검증

클래스를 사용하는 장점으로는 데이터에 추가적인 제약 조건을 반영할 수 있다는 것이다. 

1. 시, 분, 초는 모두 0보다 크거나 같아야 한다.
2. 시의 범위는 0~23, 분과 초의 범위는 0~59이다.

아래 코드는 위의 조건들을 반영하여 Time 클래스를 작성한 것이다. 제어자와 메서드에 대해 추가적인 이해가 필요하지만 이런 식으로 제약조건을 설정하고 입력에 대한 유효성을 검증할 수 있다는 것을 알아두길 바란다.

public class Time {
    private int hour;
    private int minute;
    private float second;

    public int getHour() {
        return hour;
    }

    public int getMinute() {
        return minute;
    }

    public float getSecond() {
        return second;
    }

    public void setHour(int h) {    //hour을 설정
        if (h < 0 || h > 23)    //시의 범위는 0~23 사이가 아니면
            return; //입력값 무시
        hour = h;
    }

    public void setMinute(int m) {  //minute를 설정
        if (m < 0 || m > 59)    //분의 범위가 0~59 사이가 아니면
            return; //입력값 무시
        minute = m;
    }

    public void setSecond(float s) {    //second를 설정
        if (s < 0.0f || s > 59.99f)
            return;
        second = s;
    }

    public static void main(String arg[]) {
        Time t1 = new Time();
        System.out.println(t1.getHour());   //0 출력
        System.out.println(t1.getMinute()); //0 출력
        System.out.println(t1.getSecond()); //0.0 출력

        t1.setHour(-1);
        System.out.println(t1.getHour());   //0 출력
        t1.setHour(13);
        System.out.println(t1.getHour());   //13 출력
    }
}

총평

 지금 보니까 객체지향 프로그래밍에 대한 글이 부실한 거 같아서 내용을 좀 보충했다.

 언젠가 돌아오겠다고 했는데 3개월 만에 돌아왔다. 좋은 글을 쓸 수 있도록 노력해보겠다.

댓글