PHP5 객체모델 – 10.인터페이스

인터페이스의 개념

인터페이스(interface)의 정확한 의미를 알기 위해서는 자바를 개발한 선의 자바 투토리얼(sun’s The Java Tutorial) 중에서 What Is an Interface?를 참고하는 것을 좋을 듯합니다.

이 문서에 의하면 일반적인 의미에서의 인터페이스란 “상관관계 없는 물체들간의 상호작용을 위해 사용되는 장치”라고 합니다.

프로그래머들이 생활속에서 쉽게 접할 수 있는 인터페이스라면 컴퓨터 소프트웨어를 들 수 있을 것입니다. 컴퓨터 하드웨어라는 것은 인간의 감각과는 거리가 먼 이물질이기 때문에 중간에 소프트웨어라는 인터페이스를 통해 인간의 시각, 촉각과 청각 등으로 컴퓨터 하드웨어를 손쉽게 다룰 수 있습니다.

< 인터페이스의 개념 >

이와같이 서로 다른 특성을 가진 물체를 서로 연결해 주는 매개체 역활을 하는 것이 인터페이스인 것처럼 객체 인터페이스(Object Interfaces)도 관련성 없는 객체들간에 상호작용할 수 있도록 매개역할을 하는 객체라고 할 수 있습니다.

클래스의 상속 개념과 비교할 때

상위클래스로부터 상속되는 하위클래스는 상위클래스와 상당히 밀접한 관계를 가지고 있습니다. 서로 관련없는 클래스를 상속받을 이유가 없는 것이지요.

그러나 인터페이스는 클래스와의 관계성이 중요하지 않으며 클래스는 인터페이스의 멤버를 상속 구조에 관계없이 어디에서나 자유롭게 접근할 수 있어야 합니다.

인터페이스의 개요

추상클래스는 추상메소드 뿐만 아니라 추상메소드가 아닌 멤버들도 제한없이 포함할 수 있습니다. 반면에 인터페이스의 모든 메소드는 반드시 추상메소드이어야 하며 그 구현은 인터페이스를 구현하는 클래스에서 처리하여야 합니다.

추상메소드 이외에 인터페이스에 포함될 수 있는 멤버로는 상수가 있습니다.

인터페이스의 구문형식

class 키워드 대신에 interface 키워드를 사용한다는 것을 빼면 인터페이스의 구문형식은 class 구문형식과 동일합니다.

‘인터페이스의 개념’에서도 살펴보았듯이 클래스는 상속 구조와 관계없이 인터페이스의 모든 멤버를 자유롭게 접근할 수 있어야 하기 때문에 인터페이스에 선언되는 멤버(메소드, 상수)는 반드시 public visibility를 가지고 있어야 합니다.

클래스 상수는 그 자체로 항상 public visibility 특성을 가지고 있기 때문에 const 키워드 뒤에 상수이름과 그 값을 나타내주면 됩니다.

추상클래스에서는 추상메소드와 추상메소드가 아닌 메소드를 구별하기 위하여 추상메소드를 선언할 때 반드시 abstract 키워드를 지정해 주어야 합니다. 반면에 인터페이스에는 추상메소드만 존재하기 때문에 abstract 키워드를 생략하더라도 추상메소드로 자동인식하므로 별문제 없이 정상적으로 실행됩니다.

public 키워드도 마찬가지로 인터페이스에 선언된 메소드는 무조건 public visibility이기 때문에 생략하더라도 자동적으로 public으로 인식합니다.

이와같이 인터페이스에 선언된 메소드는 abstract 또는 public 키워드를 지정하지 않더라도 그 메소드는 기본적으로 public하고 abstract한 메소드로 인식하게 됩니다.

인터페이스의 구현

클래스에서 인터페이스를 구현하려면 implements 키워드를 클래스 정의에 포함시켜야 합니다. 클래스를 상속하는 것과는 달리 인터페이스는 여러개의 인터페이스를 동시에 구현할 수 있으며, 이 때 각 인터페이스는 콤마(,)로 구분하여 표기합니다.

인터페이스를 사용하여 작성된 클래스는 인터페이스에 선언된 모든 메소드의 몸체를 구현하여야 합니다.

추상클래스를 상속받는 하위클래스는 추상클래스에 선언된 추상메소드의 구현을 선택적으로 할 수 있었습니다만 인터페이스를 사용하는 클래스는 인터페이스에 선언된 모든 추상메소드를 구현해주어야 합니다. 따라서 아래와 같이 인터페이스에 선언된 메소드 중에 하나라도 빠지게 되면 치명적인 에러(fatal error)가 발생합니다.

상속 중인 클래스에서의 인터페이스 구현

상속 중인 하위클래스에서도 아래와 같은 형식으로 인터페이스를 구현할 수 있습니다.

다중 인터페이스

객체지향 프로그래밍을 하다보면 단일 상속으로는 해결하기 어렵고 두 개 이상의 클래스로부터 멤버를 상속해야 할 경우가 많이 발생합니다. 이러한 경우에 C++과 같은 객체지향언어에서는 다중상속(multiple inheritance)을 통해 해결할 수 있습니다.

다중상속에 대하여는 “객체 환경” 카테고리의 ” Zend2객체모델 > 다중상속” 문서를 참조바랍니다.

위 문서를 보면 알겠지만 다중상속에 의해 클래스의 상속 구조가 매우 복잡해지고, 멤버 특성의 상속 관계가 모호해 질 수 있기 때문에 간단한 구조를 지향하는 자바에서는 다중상속을 허용하지 않고 대신에 다중 인터페이스를 이용하게 됩니다.

PHP5에서도 자바의 영향을 받아 단일상속만을 지원하기 때문에 상속 개념으로는 해결할 수 없으며 자바와 같이 다중 인터페이스를 통해 이러한 문제를 해결하고 있습니다.

다중 인터페이스의 메소드 이름 충돌

다중 인터페이스로 구현된 클래스를 작성하려면 먼저 각 인터페이스에서 선언된 메소드의 이름이 중복되어 선언되지 않았는지 확인하여야 합니다. 만약 동일한 이름의 멤버를 가진 인터페이스들을 다중 인터페이스하게 되면 아래와 같은 에러가 발생합니다.

Fatal error: Can’t inherit abstract function minus::same_name() (previously declared abstract in plus) …

자바의 경우에는 동일한 메소드 이름이라도 인자값의 데이터형이나 인자의 갯수가 다르면 실제로는 다른 메소드로 인식하기 때문에 문제가 발생하지 않습니다. 이것은 중복정의(overloading) 개념에 해당합니다만 PHP에서는 중복정의(overloading)를 지원하지 않기 때문에 인자의 갯수가 다르더라도 동일한 메소드로 인식하여 역시 위와 동일한 에러가 발생합니다.

중복정의(overloading)에 대하여는 “객체 환경” 카테고리의 “객체지향언어로서의 PHP > 함수 중복정의” 문서를 참조바랍니다.

인터페이스의 상속

인터페이스도 클래스와 같이 상속할 수 있습니다. 클래스를 상속할 때와 마찬가지로 인터페이스를 상속할 때도 extends 키워드를 사용합니다.

interface 인터페이스이름 extends 인터페이스이름[, 인터페이스 이름[, ……]] { …… }

인터페이스를 상속할 때는 implements 키워드를 사용하지 않고 클래스와 같이 extends 키워드를 사용하는 것에 주의하기 바랍니다.

인터페이스의 다중상속

인터페이스는 클래스와 달리 여러 개의 상위 인터페이스(super interface)를 다중상속(multiple inheritance)할 수 있습니다. 각각의 상위 인터페이스는 extends 부분에 콤마(,)로 분리하여 표기합니다.

  • interface seoul { …… }
  • interface pusan { …… }
  • interface korea_city extends seoul, pusan { …… }

인터페이스를 이용한 예제 작성

이제 한가지 예를 통해 인터페이스를 이용하는 방법에 대하여 살펴보겠습니다.

이 예제는 각 도형(삼각형, 사각형, 원형)에 대한 면적을 구하고, 각 도형별로 구한 면적들의 평균값과 최대값을 구하는 프로그램으로 인터페이스를 이용하여 작성해 보겠습니다.

figure_area 인터페이스는 단 하나의 추상메소드 get_area로 구성되어 있으며, 어떤 도형인지는 알 수 없으나 그 면적 값을 획득할 목적으로 작성된 것입니다. 이 인터페이스를 이용하여 삼각형, 사각형 및 원의 면적을 계산하고 계산된 값을 획득하는 각각의 클래스도 만들 것입니다.

figure_area 인터페이스만 보면 어떤 도형의 면적 값을 획득하는 것인지를 알 수 없으나 이러한 미지의 도형 면적 값을 추상메소드 get_area()를 통해 여러 개를 입력받아 그 모든 값의 합과 평균값을 구하고 입력된 값 중에 최대값을 구하는 calculate 클래스를 작성해 보겠습니다.

PHP에서는 데이터형을 지정하지 않으나 이 예제에서는 소스의 정확한 의미를 전달하기 위하여 각 멤버 및 각 함수 입출력값의 데이터형을 주석으로 표기하였습니다.

calculate 클래스에 정의된 add 메소드의 입력값인 $x의 데이터형(data type)은 figure_area 인터페이스입니다.

14번행에 의해 $x가 figure_area 인터페이스의 멤버인지 확인한 후 멤버가 아니라면 21번행에 의해 에러메시지를 나타낸 후 즉시 스크립트를 종료합니다.

$x가 figure_area 인터페이스의 멤버인 경우에만 합과 최대값을 구하고 카운팅한 후  add 메소드 실행를 마칩니다. 15번행부터 19번행까지의 소스를 살펴보면 최대값을 기록하는 멤버 $maximum에는 면적의 실제 최대값이 기록되는 것이 아니라 최대값을 가지고 있는 figure_area형의 객체를 기록하고 있습니다.

25번행에 정의된 get_maximum 메소드는 add 메소드를 통해 입력된 여러개의 figure_area형 객체 중에서 최대면적값을 가지고 있던 객체를 반환해 줍니다. 따라서 get_maximum 메소드의 반환되는 값의 데이터형은 figure_area입니다.

29번행에 정의된 get_average 메소드는 입력된 값의 합을 입력된 값의 수로 나눈 평균값을 반환합니다.

마지막으로 figure_area  인터페이스를 구현해줄 각 도형에 대한 클래스를 작성하겠습니다.

각 도형의 제원(가로, 세로 또는 반지름)은 아래의 예와 같이 생성자를 통해 전달됩니다.

삼각형과 사각형은 가로와 세로길이를 입력값으로, 원은 반지름을 입력값으로 지정합니다.

클래스 triangle, rectangle, circle의 생성자를 통해 각 도형의 제원이 전달되면 이 값이 각 멤버($width, $height, $radius)에 기록되며 get_area 메소드에서는 기록된 각각의 제원(가로, 세로 또는 반지름)을 가지고 각 도형에 맞는 면적을 계산하여 반환합니다.

이에 대한 실행 소스는 아래와 같습니다.

5행에서 “new triangle(4, 5)”에 의해 triangle 클래스의 객체가 생성되는 동시에 그 생성자를 통해 입력된 가로, 세로값인 4와 5가 triangle 클래스의 멤버변수 $width와 $height에 기록됩니다. 그리고 생성된 인스턴스는 바로 calculate 클래스로부터 생성된 $obj_triangle 인스턴스의 add 메소드의 입력값으로 전달됩니다. add 메소드에서 전달받은 인스턴스는 figure_area 인터페이스를 구현한 삼각형 클래스의 인스턴스가 됩니다.

결국 위 소스를 실행하게 되면 아래와 같은 결과를 얻을 수 있습니다.

calculate 클래스가 오로지 figure_area 인터페이스만을 의존하여 작성하였지만 실제로 calculate 클래스의 add 메소드에는 figure_area 인터페이스를 구현한 각 도형 클래스의 인스턴스가 전달되므로 각 도형의 면적에 대한 평균값과 최대값을 구할 수 있게 됩니다.

figure_area 인터페이스를 구현하고 있는 각 도형의 클래스와 calculate 클래스를 UML(Unified Modeling Language)로 나타내면 아래와 같습니다(UML에 관한 것은 관련 서적을 살펴보기 바랍니다).

< figure_area 인터페이스 >

이 예제에 대한 전체소스와 실행결과를 아래에 첨부합니다.

  • 예제 실행 결과

답글 남기기