Object-Oriented Programming
“객체 지향”이라는 말은 IT에 몸담고 있는 사람 모두에게 친숙한 단어입니다. 설계를 하거나 프로그래밍을 할 때, “객체 지향”으로 하라는 뜻은 무엇일까요? 그리고 클래스와 객체가 무엇이고 왜 쓰는 걸까요?
객체 지향(Object-Oriented), 왜 쓰는걸까
Python, Java, C++ 등.. 대부분의 프로그래밍 언어 문법 관련 책들을 보면 변수, 제어문 등을 배운 뒤에 클래스와 객체 등에 대해 배웁니다. 프로그래밍을 처음 시작하는 많은 분들이 제어문까지야 자연스럽게 받아들이는데, 클래스를 배우기 시작하면서 이걸 왜 써야 하나 궁금해하는 분들이 많습니다. 제어문이나 함수(메서드)만으로도 프로그래밍 할 수 있을 것 같은데, 클래스는 왜 쓰는 걸까요?
여러분이 클래스의 필요성을 느끼지 못하는 이유는 간단합니다. 이제 막 프로그래밍을 시작했고, 따라서 작성하는 프로그램이 굉장히 단순하기 때문입니다. 간단한 코드를 작성할 때는 객체 지향을 활용할 이유가 별로 없습니다. 실제로 과거에는 클래스 없이 프로그래밍을 했었습니다. 프로그래밍 언어의 역사를 보면, 현대의 프로그래밍 언어는 1950년대부터 만들어지기 시작했고, 객체 지향의 붐은 1980~1990년 대 사이에 일어났으며, 자바는 1995년에 만들어졌습니다. 이 말은, 과거에는 클래스를 활용하지 않고도 프로그래밍을 잘해왔다 라는 뜻입니다.
그런데 프로그램이 복잡해진다면 이야기가 달라집니다. 여러분이 10만 줄 이상의 코드를 클래스 없이 작성하다 보면, 뭔가 다른 방법의 필요성을 깨달을 거라 확신합니다. 엄청나게 긴 코드를 객체 지향이 아닌 다른 방식으로 작성하는 것은 매우 힘들 가능성이 높습니다.
우리가 사용하는 프로그램의 복잡성은 컴퓨터가 발명된 이래 계속해서 증가했습니다. 컴퓨터의 성능이 점점 좋아지다 보니, 동작시킬 수 있는 프로그램의 크기도 점점 커지게 되고, 따라서 소프트웨어 역시 복잡해지게 되는 것이죠. 예를 들어, Windows 95의 소스 코드는 1500만 줄 정도이며, Windows XP의 소스코드는 4000만 줄 정도입니다. 인간의 지능에는 한계가 있습니다. 같은 방법으로 더더 복잡한 프로그램을 만드는 것에는 한계가 있습니다. 다른 방법이 필요했습니다. 이 과정에서 유명해진 개발 방법론이 객체 지향입니다. 큰 규모의 소프트웨어를 만들 때, 객체 지향 방법론을 사용하면 훨씬 수월하게 작업을 진행할 가능성이 큽니다.
객체 지향이 만능은 아닙니다. 프로그램의 종류(목적)에 따라 Rule-Oriented나 Procedure-Oriented 등의 다른 접근 방식이 더 나을수도 있습니다.
객체 지향이란?
객체 지향 이전에는 프로그램을 명령어들의 목록이라는 관점에서 바라봤습니다. 소스 코드는 어떻게 동작할지를 설명합니다. 하지만 객체 지향에서는 프로그램을 객체를 중심으로 바라봅니다. 소스 코드는 이 객체가 어떻게 행동하고, 또 다른 객체는 어떤 객체와 어떻게 소통하는지를 설명합니다. 계속 객체라는 단어가 등장하는데, 객체에 대해 알아볼까요?
Object (객체)
한 가지 말씀드리고 싶은 말은, 객체는 프로그래밍에서만 사용되는 컴퓨터 공학적 개념이 아니라는 것입니다.
객체는 아래와 같은 것들을 말합니다.
- 만질 수 있거나 보이는 것
- 이해될 수 있는 것
- 생각이나 행동이 가리키는 것
우리 주변에 있는 모든 것들이 객체입니다. 제 앞에 있는 노트북, 마우스도 객체입니다. 옆에 있는 사람도 객체입니다. 저도 객체입니다. 귀신도 객체입니다. 우리가 만질 수 있거나 볼 수 있는 것, 이해될 수 있는 것 모두가 객체입니다.
모든 객체는 두 가지 구성요소, 속성(Attribute)와 행동(Behavior)이 있습니다. 저라는 객체는 머리카락 색이라는 속성이 있으며, 이 속성의 값은 검은색입니다. 눈동자도 가지고 있으며, 눈동자의 색은 검은색입니다. 저는 잠자기, 먹기, 걷기 등의 행동을 할 수 있습니다. 제 앞에 있는 핸드폰은 흰색이라는 속성을 가지고 있으며, 전원 켜기, 전원 끄기, 전화 걸기 등의 행동을 할 수 있습니다. 이렇듯 주위의 모든 객체는 속성과 행동을 가지고 있습니다.
객체 지향 프로그래밍은 이러한 객체의 개념으로 세상을 바라보고, 이것을 소스 코드로 작성합니다.
Class (클래스)
객체와 떼려야 뗄 수 없는 관계, 클래스가 있습니다.
저는 객체입니다. 사람은 클래스입니다. 제 아이폰은 객체입니다. 스마트폰은 클래스입니다. 제 눈앞에 있는 로지텍 마우스는 객체입니다. 마우스는 클래스입니다. 느낌이 오시나요? 객체는 시간과 공간에 존재하는 구체적인 실체인 반면, 클래스는 추상화, 즉 객체의 본질을 그대로 나타냅니다. 저는 존재하는 실체입니다. 따라서 검은색 머리카락, 검은색 눈동자라는 구체적인 속성값이 있습니다. 반면에, 사람이라는 클래스는 구체적인 색을 가지고 있지 않는 추상적인 개념입니다.
Procedure-Oriented vs Object-Oriented
클래스를 사용하지 않고 프로시저를 활용하여 프로그래밍 하는 방식을 Procedure-Oriented Programming이라 합니다. 객체 지향이 유명세를 타기 전에는 Procedure-Oriented 프로그래밍이 대세였습니다. 참고로 C/C++에서는 프로시저를 ‘함수’라고 말하고, 자바에서는 ‘메서드’라 부릅니다. 루틴이라고 부르는 경우도 있습니다. 프로시저 = 함수 = 메서드 = 루틴입니다.
문장을 구성하는 요소 중 가장 중요한 것은 명사와 동사입니다. 코드에서 명사의 역할을 하는 것은 변수와 구조체이고, 동사의 역할을 하는 것은 함수입니다. 이전에는 명사와 동사가 분리되어 표현될 수 밖에 없었습니다. 그러나 객체 지향에서는 명사와 동사가 클래스라는 하나의 추상화 안에 뭉쳐져있습니다. 명사와 동사를 묶어 하나의 추상화된 개념으로 표현할 수 있다는게 가장 큰 차이입니다.
이러한 근본적인 프로그래밍에 대한 접근 방식으로 인해, 큰 규모의 코드에서는 큰 차이가 나게 됩니다.
객체의 주요 특징 - Encapsulation
객체는 모든 속성과 행동을 드러낼 필요가 없습니다. 자동차를 예로 들어보면, 운전자는 자동차의 핸들, 악셀, 브레이크와 같은 인터페이스만 사용할 수 있습니다. 엔진의 특정 실린더 온도, 각 실린더의 점화 타이밍, 연료 혼합비, 배터리 셀의 개별 전압이나 내부 온도 등은 알 필요가 없습니다. 이렇듯 객체는 인터페이스와 구현이 분리되어 있습니다. 알아야만 하는 것만 외부 객체에게 드러나 있습니다.
Interface
다른 객체가 접근할 수 있는 객체 간의 소통 수단입니다. 자동차로 보자면 핸들 꺾기, 악셀 밟기, 브레이크 밟기 등의 동작입니다. 사용자에게 필요한 것들만 인터페이스로 나와있는 것이 가장 바람직합니다.
Implementation
인터페이스를 제외한 속성들과 구체적인 동작들은 구현에 포함됩니다. 실린더에 연료 분사, 엔진 냉각 시스템 동작, 엔진 연소 후 발생되는 배출가스 처리 등의 동작은 외부의 객체는 알 필요가 없는 구현의 영역입니다.
사용자 입장에서는 자동차를 운전하기 위해 세부 구현을 알 필요가 없으므로 이해가 쉬워집니다. 자동차들의 인터페이스는 같기 때문에 처음 보는 다른 자동차를 운전하는 것도 어려움이 없습니다. 또한 인터페이스가 동일하다면, 내연기관차, 하이브리드, 전기차, 후륜 구동, 전륜 구동 등의 세부 구현이 바뀌더라도 다른 객체에는 영향이 없으므로 내부 구현의 변경이 쉽습니다.
클래스들의 관계
Interitance: Is-a Relationships
Single Inheritance
상속을 통해 클래스가 다른 클래스로부터 속성과 메서드를 물려받아 사용할 수 있습니다. 이를 통해 클래스들의 공통적인 속성과 행동을 추상화하여 새로운 클래스를 만들 수 있습니다.
예를 들어, 자동차와 오토바이의 속성과 동작을 생각해 봅시다.
- 자동차
- 속성 :
속도
,실내 온도
- 동작 :
운전하기
,멈추기
,히터 켜기
,에어컨 켜기
- 속성 :
- 오토바이
- 속성 :
속도
,사이드 스탠드 상태
- 동작 :
운전하기
,멈추기
,사이드 스탠드 내리기
,사이드 스탠드 올리기
- 속성 :
그런데 자동차와 오토바이는 속도
라는 공통된 속성과, 운전하기
, 멈추기
라는 공통된 동작이 있습니다. 그래서 이러한 공통된 것을 포함하는 새로운 클래스 – 운송 수단을 만들어 공통되는 것들을 넣어놓고 자동차와 오토바이가 운송 수단으로부터 속성과 행동을 상속받도록 만들 수 있습니다.
- 운송 수단
- 속성 :
속도
- 동작 :
운전하기
,멈추기
- 속성 :
- 자동차 (운송 수단 상속)
- 속성 :
실내 온도
- 동작 :
히터 켜기
,에어컨 켜기
- 속성 :
- 오토바이 (운송 수단 상속)
- 속성 :
실내 온도
- 동작 :
히터 켜기
,에어컨 켜기
- 속성 :
위 경우 자동차와 오토바이는 운송 수단을 상속받았으므로 속도
속성과 운전하기
, 멈추기
동작을 가지고 있습니다. 이렇게 하면 중복되는 코드를 막을 수 있고, 공통된 속성이나 행동을 변경할 경우 이를 상속받은 모든 하위 클래스가 자동으로 변경을 반영하게 되므로 유지 보수가 쉽습니다. 이때 운송 수단과 같은 클래스를 Superclass(또는 parent class), 그리고 이것을 상속받아 사용하는 클래스를 Subclass(또는 child class)라고 부릅니다.
한 가지 중요한 점은, 클래스 A와 B가 주어졌을 때 A는 B가 아니라면(A is not a kind of B) A는 B의 서브 클래스가 되어서는 안됩니다. 위의 경우에는 자동차와 오토바이는 운송 수단이기 때문에 자동차와 오토바이는 운송 수단 클래스로부터 상속받도록 클래스를 설계해도 됩니다.
Polymorphism (다형성)
다형성이란 동일한 인터페이스에 대해 각각의 객체가 서로 다른 방식으로 동작하는 것을 말합니다. 만약 운송 수단에 선박이 추가된다면 어떨까요? 운송 수단을 상속받은 선박 클래스를 만든다고 할 때, 선박의 운전하기 동작은 자동차나 오토바이와는 많이 다를 겁니다. 이런 경우 선박 또한 운송 수단이기 때문에 운전하기
라는 인터페이스는 그대로 사용하지만, 선박의 운전하기 구현을 운송 수단의 운전하기 동작의 구현과는 다르게 만들 수 있습니다. 이때 슈퍼클래스에서 구현된 행동을 서브클래스에서 다르게 구현하는 것을 override
라고 합니다.
Multiple Inheritance (다중 상속)
상속을 하나의 슈퍼클래스가 아닌 여러 슈퍼클래스로부터 받을 수도 있습니다. 이 경우 서로 다른 슈퍼클래스에서 동일한 이름의 속성이나 인터페이스를 사용하는 경우, 서브클래스는 어떤 것을 사용해야 할까요? 이러한 문제는 프로그래밍 언어에 따라 다르게 처리하고 있습니다. C++은 다중 상속을 지원하지만, 대부분의 언어에서는 다중 상속을 지원하지 않습니다.
Composition: Part-of Relationships
어떤 클래스는 여러 클래스들을 가지고 있습니다. 자동차를 예로 들면, 자동차는 엔진과 바퀴를 가지고 있습니다. 그러나 엔진은 자동차가 아니고, 바퀴 역시 자동차가 아니기 때문에 “Is a” 관계가 아닙니다. 이처럼 클래스가 여러 클래스로 구성되는 것을 Compotision이라고 합니다.
객체 지향 프로그래밍을 배우고 싶다면
일단 클래스 관련 문법을 공부하신 후, 책을 보는 것을 추천드립니다.
- Object-Oriented Thought Process by Matt Weisfeld
- Head First Object-Oriented Analysis and Design by Brett McLaughlin, Gary Pollice, David West
- …
보통 위의 도서 등을 추천하는 것 같군요.
객체 지향이란 개념은 쉽지 않습니다. 클래스를 설계하는 것에 정답이 없기 때문에 더 그런 것일지도 모르겠습니다. 한 번에 객체 지향을 마스터하겠다는 생각보다는, 다른 것도 배워가면서 서서히 익혀가는 게 어떨까 싶습니다.
Leave a comment