OOP - Object Oriented Programming ch.2
<OOP의 속성과 특징들>

http://wahnfried.net






  새삼 내용을 적어내려가면서 돌이켜 보니, 이제껏 스스로도 다시는 손 댈 수 없는(untouchable) 코드를 양산했던 기억들이 새록 새록 떠오르는군요. '아직은 초짜니까' 라고 위안해 봅니다만, 알고 있는 것들 만큼이라도 업무에서 실천해야 겠다는 마음이 불끈 듭니다.

  게다가 쉽다면 쉽고 어렵다면 어려운 내용이라, 그저 실용적인 관점에서 설명하는 게 할 수 있는 전부가 될 듯하네요. 다른 분들이 적어둔 OOP에 관한 글들을 여럿 읽어 보시는 것을 추천합니다. 여기에서는 되도록 간명하고 쉽고 당장 써먹을 수 있는 그런 설명만을 남기도록 하겠습니다.





0. Review

OOP를 강조하는 이유

- 중복 코드의 제거

- 코드의 재사용

- 유지 보수의 용이성

  (따지고 보면 저 위의 것들 모두가 맞물려 있는 이야기인 터라.. 당연히 한 번 만든 코드의 재사용을 위해서는 되도록 독립적인 개별 Object로 구현해서 어디서든 가져다 쓸 수 있도록 해야 할 것이고, 그 말인즉슨 중복 코드를 여러 번 갖다 붙일 이유가 없다는 말이 되며, 한 곳만 고쳐도 변경 사항을 모두 적용할 수 있게 되니...)





1. Intro


  OOP는 기본적으로 아름답지만, 이것도 어디까지나 '경우에 따라 다른' 문제입니다. 개발 환경과 목적에 따라 고지식하리만치 지켜야 할 때도 있고, 무시하는 편이 구현 과정상 혹은 퍼포먼스상 나을 수도 있으니까요. 각설하고, '프로그래머'라는 이름을 달고 일하게 되는 사람의 입장에서 아마도 대부분은 이 OOP의 그늘을 떠날 수 없을 터, 이번에는 OOP의 특징과 속성에 대해 알아보도록 합니다.

 
보통 학교의 강의 시간에 첫 시험 문제로 등장하기 딱 좋은 내용들이지요. :-) 아래와 같이요.


OOP - 잘게 나누어 구현한다! 라는 모토 - 의 방법론적 특징

Abstraction (추상화)

Encapsulation (캡슐화)

Inheritance (상속)

Polymorphism (다형성)








2. Abstraction (추상화)


  추상화(畵) 작품을 보고 느낄 수 있듯, 추상적이라는 개념은 구체적이라는 개념과 대치되는 것입니다. 앞서 잠깐 언급했던 '자동차'를 한 번 떠올려본 뒤 "자동차는 무엇입니까?" 라는 질문을 받게 된다면 어떻게 대답할 수 있는지 생각해보세요. 아마도 여러분에게 필요한 자동차의 속성들, 아니면 그저 지금 생각나는 속성들을  합쳐 자동차라는 개념을 만들어내게 되겠지요? 대충 그것이 추상화입니다.


구현하는 과정에서의 이해를 돕기 위해 아래의 예를 보도록 하지요.


현실의 자동차

=> 도로 위에서 달린다, 도로가 아니어도 달린다, 불도 들어온다, 문도 여닫힌다 등등등...


레이싱 게임 속의 자동차

=> 도로 위에서 달릴 수 있다. 달리고 부딪힌다. (개발 시간이 남는다면 불도 켠다)


  기본적으로 무언가를 소프트웨어로 구현한다는 것은 프로그래머의 머리 속에 이미지로 둥실 떠오른 무언가를 모니터 속의 현실로 끌고 오는 과정을 말합니다. 하지만 어떤 소프트웨어가 '자동차'를 필요로 한다고 해서 현존하는 자동차의 모든 것들을 구현해 낼 필요는 절대 없게 되지요. 지금은 일단 이것을 OOP에서의 추상화 개념이라고 해 두지요. 다음 원칙을 기억하기로 합니다. (말은 쉽습니다.)


객체 구현은 최대한 간결하지만, 또 충분해야 한다.








3. Encapsulation (캡슐화)


  캡슐화라는 말은 말 그대로 한 번 껍데기를 덧씌워서 굳이 캡슐 속을 보이지 않게 한다는 말로 이해하면 쉽습니다. 예를 들어 간단한 데이터베이스를 구현하기로 했다고 칩시다. 데이터베이스가 아름다운 자료 들의 집합이라는 것을 떠올려 보세요.


학생 데이터베이스 기술 명세

- 그냥 읽고 쓰기가 가능한 데이터베이스를 구현해주세요.

- 학번 / 이름 / 주소 / 전화번호 / 점수가 포함되어야 합니다.


  간단한 것이지만 이걸 어떻게 만들면 좋을까요? 말 그대로만 코드가 작동한다면야 구조체로 작성하든 클래스로 작성하든 관계가 없습니다. 대충 구조체 배열 변수 하나를 만들어서 전달해 주었고, 어디서나 직접 변수에 읽고 쓰기가 가능합니다. 하지만 이 이후 다음과 같은 요구사항들이 늘어났다면 어떡해야 하는 걸까요?


학생 데이터베이스 기능 추가  요청

- 점수가 정수형(integer)으로도, 소수(float)형태로도 필요합니다.

- 현재 데이터 묶음이 배열(array)형태로만 되어 있는데, 검색이 필요하므로
   맵(map)으로도 바꿔 세요.

- (버그) 한 번 입력한 값이 어디선가 바뀌어버리는 것 같습니다.


  처음의 사항을 어떻게 구현해냈느냐에 따라서 매우 쉬운 재구성이 될 수도 있겠지만, 정말로 앞에서와 같이 구조체 배열 변수 하나를 던져 준 것이었다면 문제는 복잡해집니다. 점수를 정수와 소수형 두 가지로 저장해야 할까요? 배열을 맵으로 바꾸는 것만으로 이전에 데이터로 접근하던 방식을 모두 다 바꾸어야 될텐데요. 특정 번호의 값을 직접 접근해서 바꾸는 것을 무슨 수로 막는단 말입니까?


  위와 같은 비극을 막기 위해 '캡슐화'라는 멋진 처방을 하게 됩니다. 여러 가지로 캡슐화를 설명할 수 있겠지만 다음의 경우를 생각해 봅시다. 핸드폰을 사용하는 사람은 번호를 누르고 통화 버튼을 누르면 전화가 걸린다는 것만 알면 되지, 그 속의 움직임은 알고 싶어 하지 않습니다. 위의 예에서도 학생의 데이터베이스를 사용하는 사람은 원하는 값을 넣고 빼는 법만 알면 되지, 데이터의 원형이 무엇인지 알 필요도, 그 데이터의 타입때문에 신경을 쓰고 싶어 하지도 않습니다.


  다시 말 해 속에서야 어떻게 구현하든, 변경되든지에 최대한 구애받지 않고 외부의 인터페이스(interface)를 제공하는 것이 캡슐화의 시작입니다. C 함수의 '선언'과 '정의'의 종속성을 최대한 줄이는 것도 같은 맥락입니다. 이해가 되셨다면 다음의 두 가지만을 기억하고, 웹에서 getter, setter와 관련한 자료를 찾아보시면 더 좋은 공부가 될 것 같군요.


(데이터/객체 모두) 외부와 통하는 인터페이스와 내용을 분리시킨다.

-> 밖에서는 안에서 돌아가는 디테일을 굳이 알 필요가 없다.










4. Inheritance (상속)


  상속이라는 개념은 정말 멋지고 강력한 개념입니다. 똑똑한 프로그래머라면 OOP에 관한 첫 이야기에서 예를 들었던 이야기 - 자동차를 수천 대 구현해내는 - 에서 코드의 복사 + 붙여넣기 없이 훨씬 빠른 시간 내에 비슷비슷한 자동차들을 거의 이름만 바꾸다시피 구현할 수 있었을 지도 모릅니다.


간단히 말하면 다음과 같은 이야기로 생각하시면 됩니다.


자식은 부모의 형질을 이어받는다.


  이게 무슨 엉뚱한 이야기인가! 라고 생각하실 지도 모르겠습니다만, 앞서의 자동차의 이야기를 볼 때에 트레일러도 자동차입니다. 해치백도 자동차겠지요. 트럭도 자동차이구요. 그렇다면 그들 사이에는 뭔가 공통적인 성질과 특성이 존재하게 마련입니다. 이를테면 핸들이 있고 - 앞으로 나올 자동차들은 없을 수도 있겠군요 :-)  - 바퀴가 있고, 달립니다. 그렇다면 그에 대한 동작들을 '자동차' 라는 객체로 모두 만들어두고 각각의 자동차의 종류들을 수정, 보완해가면 되지 않을까요? 다음과 같이 말입니다.


자동차 : 달린다. 바퀴가 있다. 핸들이 있다.

트럭 : 자동차이다 (= 달린다. 바퀴가 있다. 핸들이 있다.) + 짐을 싣는 공간이 있다.

세단 : 자동차이다 (= 달린다. 바퀴가 있다. 핸들이 있다.) + blah blah


  위와 같이 만들 수 있다면, 기본적으로 '자동차'에 구현해 낸 내용들을 다시 타이핑하거나 심지어 매우 별 것 아니기는 하지만 copy & paste 마저 할 필요가 없게 됩니다. 더욱이 22세기의 자동차는 바퀴가 없다! 라고 하면 22세기에는 '자동차'를 구현해 둔 코드에서 '바퀴가 있다'만을 수정하면 되겠지요.


  상속이라는 개념은 이처럼 멋지고 강력한 개념이기도 하지만, 인간 사회에서 근친 상간과 같은 것들이 한 집안의 가계를 복잡하게 만들듯이 많은 문제를 양산해내기도 합니다. OOP를 통해 모두가 기대하는 중복 코드의 방지/이미 작성된 코드의 재사용 등등에 있어 멋지게 작용합니다만, 다음과 같은 경우를 볼까요?


왕이 전쟁에 나가서 죽었다. 왕에게는 전처에게서 태어난 첫째 왕자와 새 왕비에게서 태어난  둘째 왕자가 있었는데, 관습대로 죽은 왕의 아내들은 모두 큰 아들인 첫째 왕자의 부인으로  삼게 되었다.

-> Q 다음 중 옳은 것 하나를 선택하시오 : 둘째 왕자는 첫째 왕자의 ( 아들? / 동생? )


  당췌 모르겠습니다. 이런 일이 프로그램 세계에서 가능한 것인지 물어보고 싶으시겠지만, 경험상으론 지금도 어디선가 누구에게서 일어나고 있습니다. 이른바 다중 상속이라는 아름답지만 무시무시한 기능으로부터 비롯되는 문제가 그들인데, 이것은 차차 이야기하기로 합니다.


  아무튼 상속의 개념은 이렇듯 기존 객체의 형질을 이어받는다는 것. 이것이 효율적인 프로그램 세계를 만들어줄 수 있다는 것만 기억하시면 됩니다. :-)








5. Polymorphism (다형성)


  다형성은 간단히 다음과 같은 예로 생각해보도록 하지요. 의문은 많이 남겠지만, 몇몇의 의문점들은 그냥 얼버무리는 것 외에 지금은 달리 할 수 있는 일이 없을 것 같기도 합니다.


선생님이 학생들에게 '춤 춰봐' 라고 말합니다.

Tommy는 게다리춤을 춥니다.

John은 디스코를 춥니다.

Chris는 살사 댄스를 춥니다.

goo는 고개만 끄덕입니다.


  모두가 다르게 춤을 추고 있지만, 모두 춤을 추고 있는 것은 사실입니다. 이게 다형성입니다. 물론 앞선 세 가지 OOP의 특징들이 모두 그러했듯, 이 글에서 감지해내실 수 있는 각 특징들의 개념들이 이들의 전부는 아닙니다. 아무튼, 다형성이란 같은 인터페이스 - '춤 추기' - 의 구현된 내용은 객체마다 모두 다를 수 있다는 것을 의미합니다.


같은 인터페이스라도 객체마다 서로 다르게 구현될 수 있다.







6. Programming Methodology (프로그래밍 방법론)


  어떻게 프로그램을 설계하고 만들 것이냐에 관한 이야기들은 적지 않은 시간 동안 많은 분들의 고민을 통해 진행되어 왔습니다. 모두가 보다 더 만들기 좋고, 사용하기 좋고, 효율적인 창조물을 만들기 위한 치열한 고민들이었습니다. OOP는 그 산물의 하나로 자리잡은 것으로, 실제로 프로젝트를 진행하다 보면 위의 4가지 속성들이 서로 독립적이지도, 완전히 종속적이지도 않을 뿐 아니라 실제로 100% 지킬 수도, 무시할 수도 없는 복잡한 경우와 마주치기도 합니다.


  중요한 것은 좋은 프로그램을 만들고자 했던 처음의 의지를 놓지 않는 것, 그리고 자신 뿐만 아니라 다른 사람에게도 좋은 코드를 만드는 것 - 사람이 읽기 쉬운 것이 좋은 코드가 될 때도 있고, 컴퓨터가 읽기 쉬운 것이 좋은 코드가 될 때도 있습니다. - 에만 신경쓴다면 실제로 엉망 진창인 무언가를 양산하는 나쁜 프로그래머가 되지 않을 수 있습니다.


  이 정도면 대충 OOP의 특징들을 훑어 본 것 같습니다. 사실 이 주제를 놓고 이야기를 시작하더라도 수 만 명의 프로그래머들이 수십 만 가지의 개념들과 예를 들며 토론을 벌일 수 있을 정도입니다. 그리고 모두가 서로 다르게 이해하는 부분들도 여럿 존재하고 있습니다. 보다 근본적인 문제에 있어 OOP라는 것이 근본적으로 지켜질 수 있느냐, 상속이라는 것은 오히려 불필요한 게 아니냐라는 말을 하는 분들도 계십니다. 이 모든 것은 어디까지나 '경우에 따라서' 판단하는 것이 옳겠지요?


  그리고 지금의 막연한 컨셉들은 앞으로의 개발을 통해 점차 구체적이면서 통합적인 - 말 그대로 추상적이며 구체적인 - 개념들도 잡혀 갈 것입니다. 하지만 이것 하나는 정말 중요합니다. 코드를 잘 만든다는 건 빨리 만드는 것만도, 빨리 돌아가는 코드를 만드는 것만도 아닙니다. 선배나 좋은 스승에게 "개념 잡힌 프로그래밍이란 무엇일까요?"와 같은 질문을 던져 보는 것도 좋은 배움이 되겠지요. 저 역시 항상 그렇게 배우고 있으니 말입니다.