본문 바로가기

Programming Languages/C++

[클래스와 객체]-객체지향 프로그래밍의 주요 개념

C++ 언어는 객체지향 프로그래밍을 지원하는 프로그래밍 언어이다. 객체지향 언어의 효시는 1967년에 발표된 Simula67이다. 그 이름을 통해 예상할 수 있는 것처럼 Simula는 시뮬레이션(simulation)을 위해 개발된 언어이다. 시뮬레이션이란 현실세계에 존재하는 여러 가지 개체와 이들이 상호작용하는 과정을 컴퓨터를 통해 가상으로 수행하는 것이다. 이처럼 객체지향 개념의 태동이 시뮬레이션과 관계가 있다는 점은 객체지향을 이해하는데 큰 의미가 있다. 즉, 분석의 출발점이 '어떠한 프로세스를 통해 작업을 수행하는가'가 아니라 '현실세계를 시물레이션하는 컴퓨터 내의 가상세계 안에 어떠한 개체들이 존재하는가'이다.

시뮬레이션의 대상이 되는 개체들은 각각 정해진 동작을 하며, 그 과정에서 개체 각각의 내부 상태가 변화할 것이다. 객체지향 프로그래밍에서는 이러한 아이디어에 따라 대상물이 할 수 있는 행위와 대상물을 상태를 표현하는 데이터를 함께 묶어 표현한다. 이처럼 행위와 데이터를 결합하여 놓은 것이 객체지향 프로그래밍의 가장 기본적인 개념인 객체(object)이다.

객체

객체지향 언어에서 기본이 되는 개념은 객체이다. 객체는 실세계의 문제영역에 존재하는 대상물을 그 대상물의 속성(attribute)메소드(method)로 모델링한 것이다. 객체의 속성은 그 객체의 상태를 나타내는 데이터이며, 객체의 메소드는 내부의 데이터를 사용하여 정해진 동작을 하는 함수이다. 이 처럼 객체지향 방식에는 관련된 데이터와 함수들이 하나로 묶여 객체로 표현된다.

실제로 어떤 것을 객체로 모델링할 수 있는지 머릿속에 잘 떠오르지 않을 수 있다. 사실 세상에 존재하는 모든 것들이 전부 대상이며, 어느 것은 객체로 표현되고 어느 것은 안 된다는 식으로 한계를 지을 수 없다.

예를 들어 흔히 사용되는 자료구조인 스택(stack)이 객체가 될 수 있다. 스택은 값을 저장하는 메소드와 값을 꺼내는 메소드를 가지고 있고, 이를 위해 필요한 데이터 저장공간을 가지고 있다. 이때 저장한 순서의 역순으로 값을 꺼낼 수 있어야 한다(LIFO:last in, first out). 스택뿐만 아니라 큐, 트리 등 여러 가지 자료구조들이 객체로 표현될 수 있다.

물체도 객체로 표현될 수 있다. 전자회로를 시뮬레이션하는 회로 설계도구에서는 회로를 구성하는 하나하나의 소자가 객체로 표현될 수 있다. 예를 들어 계수기를 객체로 표현한다면 저장하고 있는 계수기의 값이 속성이 될 것이고, 계수기를 리셋하거나 상향 또는 하향 계수하는 등의 메소드가 포함될 것이다. 이 외에도 윈도 시스템의 윈도, 자동차 경주 컴퓨터 게임의 자동차 등 객체로 표현할 수 있는 것의 한계는 상상력의 한계라고 해도 과언이 아니다.

메시지

하나의 프로그램에 객체가 하나만 존재하는 경우는 거의 없다. 일반적으로 여러 개의 객체가 포함되어 있으며, 프로그램이 필요한 기능을 해내기 위해서는 이들이 상호작용해야만 한다. 한 객체가 다른 객체의 기능을 필요로 한다면 그 객체는 상대방 객체에게 필요한 작업이 이루어지도록 요구한다. 이와 같이 상대방 객체에게 필요한 작업을 실행하도록 요구하는 것을 객체지향 프로그래밍에서는 메시지를 보낸다고 한다.

그럼다면 메시지는 어떻게 보낼까? 객체지향 언어에서 메시지를 보내는 것은 그 메시지를 처리할 메소드, 즉 함수를 호출하는 것을 의미한다. 만일 이 과정에서 전달해야 할 데이터가 있다면 함수의 인수를 통해 전달한다.

클래스

객체를 하나의 물체라고 한다면 그 물체의 설계도와 같은 것이 있을 것이다. 클래스(class)는 마치 객체의 설계도와 같다. 설계도에 기초하여 물건을 만들어 내듯이, 클래스에는 객체가 포함할 속송애 대한 명세와 메소드들이 정의되어 있어, 클래스를 이용하여 동일한 속성 및 메소드를 갖는 객체들을 생성한다. 클래스 자체에는 데이터가 저장되지 않지만, 클래스에 의해 생성된 객체는 각각 고유한 속성값을 갖는다. 그래서 객체를 클래스의 사례(instance)라고 부르기도 한다.

[그림 4-1] 클래스는 객체를 만들어 내는 설계도와 같은 것으로, 그 클래스에 해당되는 객체들이 갖는 속성과 메소드가 클래스 안에 정의되어 있다.

[그림 4-1]은 클래스와 객체의 관계를 보여 준다. 계수기를 나타내는 Counter 클래스에는 속성과 메소드들이 정의된다. 즉, 계수기의 값(value)이 속성으로 정의되어 있고, 계수기의 값을 0으로 지우기(reset), 1 증가시키기(count), 값을 알려주기(getValue) 등의 메소드가 정의되어 있다. 각각의 속성은 어떤 자료형으로 데이터를 저장할 것인가에 대한 사항이 자세히 정의되며, 메소드를 구현하는 함수가 정의된다. 즉, Counter 클래스는 계수기 객체가 어떤 데이터를 저장하고 어떻게 동작하는가를 설계하여 놓은 것이다. 그러나 Counter 클래스를 정의했다고 해서 실제 계수기가 존재하는 것은 아니다. 이 클래스를 이용하여 객체를 생성하면 값을 저장하기 위한 메모리 공간이 할당되면서 비로소 실체를 갖는 계수기 객체가 생성되는 것이다.

캡슐화

캡슐화(encapsulation)는 객체의 사용자 측면의 사항과 설계자 측면의 사항을 분리하는 것이다. 객체의 사용자에게는 객체에게 어떤 메시지를 어떻게 보냄으로써 그 객체가 동작하게 할 수 있는가에 대한 정보가 필요하며, 세부적인 객체 내부 상태의 표현이나 행위의 구현에 대해서는 알 필요가 없다. 반면 설계자는 객체가 동작하게 하기 위한 세부적인 구현 부분을 정의해야 할 것이다. 캡슐화는 이와 같이 객체 내부의 구현에 대한 사항은 감추고, 외부로는 공개된 메소드를 통해 인터페이스를 제공하여 객체를 사용할 수 있게 하는 것을 의미한다. 이와 같이 캡슐화된 객체 내부의 세부 사항을 사용자가 직접 사용할 수 없도록 감추는데, 이를 정보 은닉(information hiding)이라고 한다. 객체 지향 프로그래밍 언어에서는 캡슐화를 위해 공개할 멤버와 공개하지 않을 멤버를 구분할 수 있다. 

캡슐화는 마치 텔레비전 사용자가 텔레비전 내부의 회로가 어떻게 동작하는지 알지 못해도 전원 켜짐/꺼짐 단추, 채널 변경 단추, 음량 조절 단추 등을 사용하여 방송을 시청할 수 있는 것과 같다. 실제로 텔레비전을 동작하게 하는 전자회로는 텔레비전 내부에 감추어져 있어 일반 사용자가 회로를 직접 조작할 수 없다. 사용자가 텔레비전 외부의 단추나 리모컨(공개된 인터페이스)을 이용하여 조작하면 내부의 회로(감추어진 내부 구현)가 동작하여 사용자가 원하는 방송을 보여 준다. 

이러한 캡슐화의 장점은 프로그램의 다른 부분에 영향을 미치지 않고 객체의 내부 구현 방법을 수정할 수 있다는 것이다. 이러한 특성은 프로그램을 작성하고, 오류를 수정하며, 프로그램을 유집 · 보수하는 작업을 더 쉽게 만든다. 또한 이처럼 독립적으로 만들어지고 엄격한 테스트를 통해 검증된 클래스는 다른 프로그램에서도 활용횔 수 있다. 즉, 코드의 재사용 가능성(reusabillity)이 좋다.

상속

유사한 유형의 클래스가 여러 개 있을 경우 이 클래스들에는 공통적인 내용과 각 클래스별로 고유한 내용을 갖게 된다. 예를 들어 '학생'을 표현하는 클래스와 '직장인'을 표현하는 클래스가 있다면 이들을 모두 '사람'이므로 '사람'이 갖는 일반적인 속성과 메소드(이름, 생년월일, 주소, 식사, 취침 등)를 공통적으로 갖는다. 반면 '학생'은 '직장인'이 갖지 않는 고유한 속성이나 메소드(학과, 학년, 수강신청 등)를 가질 수 있고, '직장인' 역시 '학생'이 갖지 않는 고유한 속성이나 메소드(소속부서, 직책, 문서 기안 등)를 가질 수 있다. 이때 이 클래스들의 공통적인 사항을 포함하는 클래스를 만들고, 개별 클래스들은 이를 공유하면서 각자에 고유한 사항만 포함하도록 설계할 수 있다.

이러한 방식으로 설계된 클래스들은 계층적인 관계를 갖게 된다. 상위 계층의 클래스에는 일반적인 속성 및 메소드를 정의하고, 하위 계층의 클래스에는 특수한 속성 및 메소드를 정의한다. 이때 하위 클래스는 자신의 특수한 속성 및 메소드뿐만 아니라 상위 클래스에 정의된 일반적인 속성 및 메소드도 동시에 가지게 된다. 이와 같이 상위 클래스의 속성 및 메소드를 하위 클래스가 이어받는 것을 상속(inheritance)이라고 부른다. 이러한 계층관계에서 상위 클래스를 기초 클래스(bass class), 슈퍼 클래스, 부모 클래스 등으로 부르고, 상속을 받은 하위 클래스를 파생 클래스(derived class), 유도 클래스, 서브 클래스, 자식 클래스 등으로 부른다.

상속을 활용하면 공통적인 내용을 중복하여 정의할 필요가 없게 된다. 또한 다형성, 동적 바인딩 등의 유용한 기법을 활용할 수 있다.