객체지향의 배경
코드를 작성하는 방법은 프로그램을 만드는데 필요한 수고를 줄이는 방향으로 발전해왔다. 사실 프로그래밍을 하는 사람이면 누구나 알고 있는 변수, 반복문, 함수, 파일 인클루드는 로직을 재활용하기 도입된 일종의 편의 기능이라고 할 수 있다. 이것들 없이도 프로그램을 만들 수 있지만, 이것들 없이는 규모있고, 버그 없는 프로그램을 만드는 것이 대단히 어렵다.
객체지향 프로그래밍
객체지향도 어찌보면 그 연장선상에 있다. 객체지향이란 영어로 Object-oriented programming, 줄여서 OOP라고 부르는 프로그래밍의 패러다임으로 변수나 반복문, 함수와 같은 기존의 프로그래밍 패러다임을 대체하는 것이 아니고, 프로그래밍 언어로 만들어진 로직들을 좀 더 부품답게 만드는 기법들이라고 할 수 있다. 그럼 여기서 말하는 '부품답게'는 무엇을 말하는 것일까?
모듈화
'부품답게'를 다른말로는 모듈화라고 할 수 있다. 이해를 돕기 위해서 비유를 해보자. 아래에 스탠드가 있다. 이 스텐드에는 모듈화의 다양한 장점이 함축되어있다. 스탠드를 만들 때 가장 생각하기 쉬운 방법은 하나의 덩어리로 만드는 것이다. 다시 말해서 스탠드의 본체와 전구를 일체형으로 만드는 것이다. 그런데 이런식으로 스탠드를 만드는 경우는 많지 않다. 이렇게하면 전구가 고장나면 스탠드를 버려야 한다. 스탠드 본체가 고장나도 전구를 버려야 한다. 또 LED와 같이 저전력의 전구로 교체하고 싶어도 그렇게 할 수 없다. 이런 문제를 해결하기 위해서 스탠드의 본체와 전구를 분리하게 되는데, 이것이 전구와 스탠드의 부품화 다시 말해서 모듈화라고 할 수 있다.
Class 와 Instance
객체 지향 프로그래밍에서 모듈화를 하는 방법을 알아보자. 예를들어 LED 전구를 부품으로 만들어보자. 일반적으로 부품을 만들 때 제일먼저하는 것은 그 부품의 설계도를 그리는 것이다. 그리고 그 설계도를 구현한 구체적인 제품을 만들게 된다. 객체 지향 프로그래밍에서 설계도에 해당하는 것이 클래스이고, 그 설계도를 구현한 구체적인 제품을 Object(객체) 혹은 인스턴스(instance)라고 부른다.
아래의 코드는 LED 전구에 대한 설계도를 프로그래밍적으로 만든 것이다. 아래 내용에서 Class LED 뒤에 따라오는 중괄호가 LED가 동작하는 방법에 대한 설계도이고, new LED 가 LED의 설계도를 실제로 구현한 제품 다시 말해서 인스턴스다. 코드 내에 주석으로 상세한 설명을 첨부했다. 꼼꼼하게 읽어보자. 이 내용은 위에서 아래로 읽어나가면 이해될 수 있는 순차적인 지식이 아니라, 전체적으로 받아들여야 이해되는 총체적인 내용이다. 그러니까 한번 죽 읽고 끝내지 말고, 최소 3번 이상 반복적으로 읽으면서 클래스와 인스턴스의 관계에 대해서 생각해보자.
<?php // LED라는 이름의 클래스를 만든다. // 클래스는 일종의 설계도와 같은 것이다. class LED { // 클래스 안 함수 밖에서 선언된 변수는 보통 클래스 변수라고 부른다. // 클래스 변수는 함수 안에서 $this->color로 접근한다. // private는 지금을 몰라도 된다. private $color; // __construct는 생성자로 클래스가 인스턴스화 될 때 가장 먼저 한번 실행되도록 약속되어 있는 특수한 함수다. // 일종의 초기화 작업을 하는데 아래 로직에서는 color를 받아서 전구의 색상을 정의하는 초기화를 하고 있다. function __construct($_color) { // $this는 클래스 내부적으로 통용되는 단위다. // $this->color는 클래스 내의 어떤 함수에서도 사용 할 수 있다. // 반면에 $this가 없는 $_color은 __construct 함수 내에서만 사용 할 수 있다. $this->color = $_color; } // function은 한국어로는 함수라고 하지만, 객체지향에서는 메소드(method)라고 부른다. // public은 지금은 몰라도 된다. public function on() { // $this->color 는 위의 생성자의 인자인 $_color의 값이 셋팅되어 있다. print("{$this->color}, 5 watt\n"); } public function off() { print("off\n"); } } // new는 객체를 생성하는 연산자로 LED라는 이름의 클래스를 설계도로 하는 객체를 만들어서 $led라는 이름의 변수에 활당하고 있다. // 'new LED'를 통해서 만들어진 $led_yellow 내의 로직을 일반적으로 객체(object) 혹은 인스턴스(instance)라고 부른다. // 객체를 생성할 때 LED의 인자로 yellow를 전달하면 __construct의 첫번째 인자의 값으로 'yellow'가 전달되고, 이것은 결과적으로 on 메소드를 호출 했을 때 'yellow, 5 watt'를 출력하게 한다. $led_yellow = new LED('yellow'); $led_yellow->on(); $led_yellow->off(); // 아래는 LED라는 클래스의 인스턴스를 $led_white에 활당하고 있다. $led_yellow 인스턴스와 같은 클래스이지만 생성자로 전달된 값이 'white'로 다르기 때문에 $led_white->on()은 'white, 5 watt'를 출력하게 된다. // LED라는 클래스의 on과 off는 동일한 메커니즘으로 동작하지만 $led_white->on();과 $led_yellow->on();은 $this->color의 값이 다르다. // 그렇기 때문에 동일 메커니즘에 다른 결과를 출력하게 된다. $led_white = new LED('white'); $led_white->on(); $led_white->off(); ?>
위의 코드를 통해서 하나의 설계도(class LED)를 공유하는 구체적인 제품($led_yellow, $led_white)을 만들었다. 정리해보면 설계도를 클래스라고 하고, 제품을 인스턴스라고 한다. 그리고 각각의 제품은 서로 다른 상태(yellow, white)를 가지고 있기 때문에 동일한 메커니즘으로 서로 다른 결과를 리턴하고 있다. 모듈화의 목적은 프로그램을 구성하는 로직들을 재활용 가능한 단위로 쪼개고 결합해서 '부품답게' 만드는 것이라고 할 수 있다. 객체지향을 공부하고 구현하면서 계속해서 묻게 될 것이다.
"과연 이 로직을 어떻게 하면 부품답게 할 수 있을까?"
인터페이스
객체를 만들었으니 객체지향이 어떻게 부품다움에 기여하는지를 좀 더 생각해보자. 위의 비유에서 스탠드와 전구를 분리해서 각각을 서로에 대한 부품으로 만들었다. 그런데 부품을 만들기 위해서 선행되어야 할 것이 있다. 바로 표준화다. 아래 그림은 소켓과 전구의 관계를 보여준다. 스탠드는 본체와 전구 사이에 소켓이라는 표준화된 연결점이 있기 때문에 서로 다른 전구를 스탠드의 본체에 연결 할 수 있는 것이다. 이러한 연결방식은 1909년 에디슨에 의해 만들어졌고, 에디슨 소켓이라고 이름지어졌다. 즉, 같은 지름과 깊이 그리고 같은 각도의 나선을 공유하고 있는 전구만이 이 소켓에 연결되도록 강제하고 있는 것이다. 이것은 산업에서는 일반적으로 표준이라고 하고, 객체지향에서는 인터페이스라고 부른다.
객체 지향 프로그래밍과 인터페이스
그럼 부품화의 핵심이라고 할 수 있는 인터페이스를 객체지향에서는 어떻게 구현하고 있을까? 아래 코드를 보자. 코드의 주석을 꼼꼼하게 읽어보자.
<?php // interface는 설계를 강제 할 수 있다. // 아래의 iSocket를 사용하고 있는 클래스는 빈드시 on이라는 이름의 메소드를 구현해야 한다. interface iSocket { // 인터페이스의 메소드는 그 메소드의 형태를 강제하는 것이기 때문에 구체적인 구현이 들어가지 않는다. public function on(); public function off(); } // implements iSocket는 LED 클래스가 iSocket 인터페이스를 구현하고 있다는 의미다. // 이것은 LED 클래스가 iSocket 인터페이스의 on, off 메소드를 가지고 있다는 것을 보증한다. // 만약 LED 클래스가 on, off 메소드를 가지고 있지 않다면 실행이 되지 않을 것이다. class LED implements iSocket { private $color; function __construct($_color) { $this->color = $_color; } public function on() { print("{$this->color}, 5 watt\n"); } public function off() { print("off\n"); } } class Lightbulb implements iSocket { // Ligthbulb 클래스는 iSocket 인터페이스를 구현하고 있기 때문에 반드시 on 메소드를 가지고 있어야 한다. // 하지만 아래와 같이 on 대신 turn_on과 같은 이름을 가지고 있기 때문에 오류가 발생한다. // 이것을 수정하기 전까지 프로그램은 실행되지 않는다. public function trun_on() { print("yellow, 60 watt\n"); } public function trun_off() { print("off\n"); } } $led_yellow = new LED('yellow'); $led_yellow->on(); $led_yellow->off(); $lightbulb_white = new Lightbulb(); $lightbulb_white->turn_on(); $lightbulb_white->turn_off(); ?>
위의 코드를 실행하면 아래와 같은 오류가 발생할 것이다.
Fatal error: Class Lightbulb contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (iSocket::on, iSocket::off) in D:\dev\phpoop\aboutoop\interface.php on line 42 Call Stack: 0.0003 131736 1. {main}() D:\dev\phpoop\aboutoop\interface.php:0
그것은 Lightbulb 클래스가 iSocket 인터페이스를 구현하고 있음에도 on, off라는 이름의 메소드를 구현하고 있지 않기 때문이다. 이것은 마치 백열전구가 에디슨 스크류의 규격을 따르지 않고 있는 것과 다르지 않다. 위의 코드가 제대로 동작하게 하기 위해서는 아래와 같이 코드를 변경해야 한다.
<?php interface iSocket { public function on(); public function off(); } class LED implements iSocket { private $color; function __construct($_color) { $this->color = $_color; } public function on() { print("{$this->color}, 5 watt\n"); } public function off() { print("off\n"); } } class Lightbulb implements iSocket { // iSocket의 on 메소드를 구현하고 있기 때문에 규격을 만족시키고 있다. public function on() { print("yellow, 60 watt\n"); } public function off() { print("off\n"); } } $led_yellow = new LED('yellow'); $led_yellow->on(); $led_yellow->off(); $lightbulb_white = new Lightbulb(); $lightbulb_white->on(); $lightbulb_white->off(); ?>
이로서 LED와 백열전구는 같은 이름의 API를 가지게 됐기 때문에 서로 교체 가능하게 되었다. 다시 말해서 좀 더 부품답게 된 것이다.
결론
필자는 '부품답게'라는 것을 객체지향의 효용으로 강조했지만 이것은 이해를 돕기 위한 것일 뿐 객체지향 프로그래밍의 효용은 사실 다면적이다. 후속 수업을 통해서 객체지향의 음미해보자.