phpclass/객체모델2005. 10. 11. 12:27
작성: 2008.09.05
final 키워드
final 키워드는 해당 메소드와 클래스를 변경할 수 없게 보호해 주는 역할을 합니다. final로 정의된 메소드는 재정의(overriding;오버라이딩)할 수 없으며, final로 정의된 클래스는 상속이 불가능하여 자식 클래스를 만들 수 없습니다. 이와 같이 final 키워드를 이용하면 메소드를 재정의하거나 클래스를 상속할 수 없기 때문에 메소드 또는 클래스의 내용을 적절히 보호할 수 있습니다.
클래스 내의 일부 메소드만 그 내용을 숨기려면 해당 메소드명 앞에 final 키워드를 붙이면 되고, 클래스 전체 내용을 숨기려면 클래스명 앞에 final 키워드를 붙이면 됩니다. 클래스에 final 키워드를 붙이면 해당 클래스 내의 각각의 메소드에는 final 키워드를 붙일 필요가 없이 모두 생략할 수 있습니다.
[메소드에 final 키워드 사용] 자식 클래스에서 재정의(overriding;오버라이딩)를 할 수 없음
[클래스에 final 키워드 사용] 상속이 불가능하여 자식 클래스를 만들 수 없음
자바의 경우에는 멤버 변수(필드)에도 final을 붙일 수 있는데 이는 PHP에서 멤버상수를 만들기 위하여 const를 붙이는 것과 같은 역할을 합니다.
C++의 경우에는 final 키워드의 필요성이 그리 크지 않다고 생각하여 final 키워드를 언어차원에서 지원하지 않는다고 합니다. 각 언어에 적용되는 키워드를 비교하면 아래와 같습니다.
< 각 언어별 적용 키워드 >
적용대상 C++ Java PHP
멤버변수 const final const
메소드 - final final
클래스 - final final
Final 메소드
Final 메소드는 클래스를 상속할 때 재정의(overriding;오버라이딩)할 수 없도록 구현된 메소드입니다.
[code php;gutter:true] <?php class BaseClass { final public function test() { echo "BaseClass::test() called\n"; } } class ChildClass extends BaseClass { public function test() { echo "ChildClass::test() clased\n"; } } // Fatal error: Cannot override final method BaseClass::test() ?> [/code]
< 재정의할 수 없는 Final 메소드 >
부모 클래스에서 정의된 Final 메소드는 그 구현이 변경되지 않도록 보호하기 위한 것이므로 만약 위의 예의 9행과 같이 Final 메소드를 재정의(overriding;오버라이딩)하려고 시도하게 되면 14행과 같은 에러가 발생합니다.
[code php;gutter:false] <?php class BaseClass { public function test() { echo "BaseClass::test() called\n"; } } class ChildClass extends BaseClass { final public function test() { echo "ChildClass::test() called\n"; } } // Call the override function in child class ChildClass::test(); ?> [/code]
< 자식클래스에서 정의한 Final 메소드 >
final & private
final과 private는 서로 다른 역할을 수행하는 modifier이지만 Java Language Specification-Second Edition(http://java.sun.com/docs/books/jls/second_edition/html /jTOC.doc.html)의 8.4.3.3 내용을 살펴보면 알 수 있듯이 자식 클래스에서 상속받을 수 없다는 면에서는 비슷합니다.
8.4.3.3 final Methods

A method can be declared final to prevent subclasses from overriding or hiding it. It is a compile-time error to attempt to override or hide a final method.

A private method and all methods declared in a final class (§8.1.1.2) are implicitly final, because it is impossible to override them. It is permitted but not required for the declarations of such methods to redundantly include the final keyword.

It is a compile-time error for a final method to be declared abstract.
< final & private modifier >
그러나 상속할 수 없다는 면에서는 비슷하지만 동일하지는 않습니다. final 메소드를 자식 클래스에서 재정의하면 위에서 살펴보았듯이 재정의할 수 없다는 Fatal 에러를 발생시키지만 private 메소드의 경우는 아무런 에러도 발생시키지 않습니다. 잘 동작합니다. 그렇다고 private 메소드가 자식 클래스에서 상속받아 재정의 된 것은 아닙니다. private 메소드는 상속되지 않습니다. 상속되지 않는다는 것과 상속할 수 없다는 것은 전혀 다른 의미입니다.
6.6.8 Example: private Fields, Methods, and Constructors

A private class member or constructor is accessible only within the class body in which the member is declared and is not inherited by subclasses.
< private modifier >
자식 클래스에서 부모 클래스의 private로 정의된 메소드와 동일한 이름의 메소드를 정의하더라도 이 메소드는 부모 클래스의 private 메소드와는 전혀 상관없는 새로운 메소드입니다. 왜냐하면 부모 클래스의 private 메소드는 자식 클래스로 상속되지 않기 때문에 자식 클래스에서 볼 때는 부모 클래스의 private 메소드는 없는 것과 같습니다.
[code php;gutter:true] <?php class BaseClass { private function test() { echo "BaseClass::test() called\n"; } } class ChildClass extends BaseClass { public function test() { echo "ChildClass::test() called\n"; } } // Output ChildClass::test() called ChildClass::test(); ?> [/code]
9행에서 부모 클래스에서 private로 정의된 메소드와 같은 이름의 메소드를 정의하였지만 전혀 에러가 발생하지 않았으며 15행에서 보듯이 자식 클래스에서 새롭게 정의된 메소드를 잘 실행하고 있습니다.
Final 클래스
Final 클래스는 더 이상 상속할 수 없는, 즉 자식 클래스를 만들 수 없는 클래스입니다. Final 클래스의 모든 메소드는 저절로 final 메소드가 됩니다.
[code php;gutter:true] <?php final class BaseClass { public function test() { echo "BaseClass::test() called\n"; } // Here it doesn't matter if you specify the function as final or not final public function moreTesting() { echo "BaseClass::moreTesting() called\n"; } } class ChildClass extends BaseClass { } // Fatal error: Class ChildClass may not inherit from final class (BaseClass) ?> [/code]
< Final 클래스의 예(출처: php.net) >
13행과 같이 Final 클래스를 상속하려고 시도하면 14행과 같은 에러가 발생합니다.
Final 필드
자바에만 있는 개념으로 필드는 클래스 내부에 존재하는 변수를 의미합니다. Final 필드인 경우에는 필드 내용을 변경할 수 없습니다. 이와같이 자바에서는 final 키워드를 사용하여 상수를 만들 수 있습니다.
[code java;gutter:false] final float pi = 3.141592; final boolean status = true; final int tall = 176; final int[] numbers = {1, 2, 3, 4, 5}; [/code]
< 자바에서의 Final 필드 >
이러한 자바의 Final 필드는 PHP에서 상수와 같은 개념이지요.
[code php;gutter:true] <?php class MyClass { const pi = 3.141592; const status = true; const tall = 176; // Fatal error: Arrays are not allowed in class constants const numbers = array(1, 2, 3, 4, 5); } echo MyClass::pi . "\n"; echo MyClass::status . "\n"; echo MyClass::tall . "\n"; echo MyClass::numbers . "\n"; ?> [/code]
< PHP에서의 상수 정의와 사용법 >
자바와는 달리 PHP에서는 7행, 13행에서와 같이 배열 상수를 정의하여 사용할 수 없으므로 에러가 발생합니다. 참고로 자바에서의 배열 상수는 다른 형식의 상수와 좀 다른 의미를 가지고 있습니다. 배열명(변수형식, 차원, 배열크기 ...) 자체가 상수라는 의미이며 각 배열 항목의 값은 언제든지 변경할 수 있습니다.

Posted by 방글24
phpclass/객체모델2005. 10. 11. 12:24
written: Oct 20 2005
인터페이스의 개념
인터페이스(interface)의 정확한 의미를 알기 위해서는 자바를 개발한 선의 자바 투토리얼(sun's The Java Tutorial) 중에서 What Is an Interface?를 참고하는 것을 좋을 듯합니다.
이 문서에 의하면 일반적인 의미에서의 인터페이스란 "상관관계없는 물체들간의 상호작용을 위해 사용되는 장치"라고 합니다.
프로그래머들이 생활속에서 쉽게 접할 수 있는 인터페이스라면 컴퓨터 소프트웨어를 들 수 있을 것입니다. 컴퓨터 하드웨어라는 것은 인간의 감각과는 거리가 먼 이물질이기 때문에 중간에 소프트웨어라는 인터페이스를 통해 인간의 시각, 촉각과 청각 등으로 컴퓨터 하드웨어를 손쉽게 다룰 수 있습니다.
< 인터페이스의 개념 >
이와같이 서로 다른 특성을 가진 물체를 서로 연결해 주는 매개체 역활을 하는 것이 인터페이스인 것처럼 객체 인터페이스(Object Interfaces)도 관련성없는 객체들간에 상호작용할 수 있도록 매개역할을 하는 객체라고 할 수 있습니다.
클래스의 상속 개념과 비교할 때
상위클래스로부터 상속되는 하위클래스는 상위클래스와 상당히 밀접한 관계를 가지고 있습니다. 서로 관련없는 클래스를 상속받을 이유가 없는 것이지요.
그러나 인터페이스는 클래스와의 관계성이 중요하지 않으며 클래스는 인터페이스의 멤버를 상속 구조에 관계없이 어디에서나 자유롭게 접근할 수 있어야 합니다.
인터페이스의 개요
추상클래스는 추상메쏘드 뿐만 아니라 추상메쏘드가 아닌 멤버들도 제한없이 포함할 수 있습니다. 반면에 인터페이스의 모든 메쏘드는 반드시 추상메쏘드이어야 하며 그 구현은 인터페이스를 구현하는 클래스에서 처리하여야 합니다.
추상메쏘드 이외에 인터페이스에 포함될 수 있는 멤버로는 상수가 있습니다.
인터페이스의 구문형식
class 키워드 대신에 interface 키워드를 사용한다는 것을 빼면 인터페이스의 구문형식은 class 구문형식과 동일합니다.
'인터페이스의 개념'에서도 살펴보았듯이 클래스는 상속 구조와 관계없이 인터페이스의 모든 멤버를 자유롭게 접근할 수 있어야 하기 때문에 인터페이스에 선언되는 멤버(메쏘드, 상수)는 반드시 public visibility를 가지고 있어야 합니다.
interface 인터페이스이름 {
  /**
  * 상수 선언
  */
  const 상수이름 = 상수값;

  /**
  * 메쏘드 선언
  */
  [abstract] [public] function 메쏘드이름(인수리스트);
}
클래스 상수는 그 자체로 항상 public visibility 특성을 가지고 있기 때문에 const 키워드 뒤에 상수이름과 그 값을 나타내주면 됩니다.
추상클래스에서는 추상메쏘드와 추상메쏘드가 아닌 메쏘드를 구별하기 위하여 추상메쏘드를 선언할 때 반드시 abstract 키워드를 지정해 주어야 합니다. 반면에 인터페이스에는 추상메쏘드만 존재하기 때문에 abstract 키워드를 생략하더라도 추상메쏘드로 자동인식하므로 별문제 없이 정상적으로 실행됩니다.
public 키워드도 마찬가지로 인터페이스에 선언된 메쏘드는 무조건 public visibility이기 때문에 생략하더라도 자동적으로 public으로 인식합니다.
이와같이 인터페이스에 선언된 메쏘드는 abstract 또는 public 키워드를 지정하지 않더라도 그 메쏘드는 기본적으로 public하고 abstract한 메쏘드로 인식하게 됩니다.
인터페이스의 구현
클래스에서 인터페이스를 구현하려면 implements 키워드를 클래스 정의에 포함시켜야 합니다. 클래스를 상속하는 것과는 달리 인터페이스는 여러개의 인터페이스를 동시에 구현할 수 있으며, 이 때 각 인터페이스는 콤마(,)로 구분하여 표기합니다.
class 클래스이름 implements 인터페이스이름[, 인터페이스이름[, ......]] {
  /**
  * 인터페이스에 선언된 모든 메쏘드를 구현
  */
  ......
}
인터페이스를 사용하여 작성된 클래스는 인터페이스에 선언된 모든 메쏘드의 몸체를 구현하여야 합니다.    
[code php;gutter:false] <?php /** * declare the interface 'my_interface' */ interface my_interface { public function set_variable($name, $value); public function get_variable($name); } /** * implement the interface */ class my_class implements my_interface { private $vars = array(); public function set_variable($name, $value) { $this->vars[$name] = $value; } public function get_variable($name) { return $this->vars[$name]; } } ?> [/code]
< 인터페이스의 구현 예제 >
추상클래스를 상속받는 하위클래스는 추상클래스에 선언된 추상메쏘드의 구현을 선택적으로 할 수 있었습니다만 인터페이스를 사용하는 클래스는 인터페이스에 선언된 모든 추상메쏘드를 구현해주어야 합니다. 따라서 아래와 같이 인터페이스에 선언된 메쏘드 중에 하나라도 빠지게 되면 치명적인 에러(fatal error)가 발생합니다.
[code php;gutter:false] <?php interface my_interface { public function set_variable($name, $value); public function get_variable($name); } /** * This will not work * Fatal error Class my_class contains 1 abstract methods * and must therefore be declared abstract (my_interface::get_variable) */ class my_class implements my_interface { private $vars = array(); public function set_variable($name, $value) { $this->vars[$name] = $value; } } ?> [/code]
< 잘못 구현된 문장 >
상속 중인 클래스에서의 인터페이스 구현
상속 중인 하위클래스에서도 아래와 같은 형식으로 인터페이스를 구현할 수 있습니다.
class 클래스이름 extends 상위클래스이름 implements 인터페이스이름[, 인터페이스이름[, ......]] {
  /**
  * 인터페이스에 선언된 모든 메쏘드를 구현
  */
  ......
}
다중 인터페이스
객체지향 프로그래밍을 하다보면 단일 상속으로는 해결하기 어렵고 두 개 이상의 클래스로부터 멤버를 상속해야 할 경우가 많이 발생합니다. 이러한 경우에 C++과 같은 객체지향언어에서는 다중상속(multiple inheritance)을 통해 해결할 수 있습니다.
다중상속에 대하여는 "상단메뉴 >> 클래식&객체 > Zend2객체모델 > 다중상속"을 참조바랍니다.
위 문서를 보면 알겠지만 다중상속에 의해 클래스의 상속 구조가 매우 복잡해지고, 멤버 특성의 상속 관계가 모호해 질 수 있기 때문에 간단한 구조를 지향하는 자바에서는 다중상속을 허용하지 않고 대신에 다중 인터페이스를 이용하게 됩니다.
PHP5에서도 자바의 영향을 받아 단일상속만을 지원하기 때문에 상속 개념으로는 해결할 수 없으며 자바와 같이 다중 인터페이스를 통해 이러한 문제를 해결하고 있습니다.
[code php;gutter:false] <?php /** * 더하기 계산 인터페이스 */ interface plus { abstract public function calc_plus($arg1, $arg2); } /** * 빼기 계산 인터페이스 */ interface minus { abstract public function calc_minus($arg1, $arg2); } /** * 계산기 클래스 */ class calc implements plus, minus { private $value; public function calc_plus($arg1, $arg2) { $this->value = $arg1 + $arg2; } public function calc_minus($arg1, $arg2) { $this->value = $arg1 - $arg2; } public function result() { return $this->value; } } ?> [/code]
< 다중 인터페이스 예제 >
다중 인터페이스의 메쏘드 이름 충돌
다중 인터페이스로 구현된 클래스를 작성하려면 먼저 각 인터페이스에서 선언된 메쏘드의 이름이 중복되어 선언되지 않았는지 확인하여야 합니다. 만약 동일한 이름의 멤버를 가진 인터페이스들을 다중 인터페이스하게 되면 아래와 같은 에러가 발생합니다.
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 { ...... }
인터페이스를 이용한 예제 작성
이제 한가지 예를 통해 인터페이스를 이용하는 방법에 대하여 살펴보겠습니다.
이 예제는 각 도형(삼각형, 사각형, 원형)에 대한 면적을 구하고, 각 도형별로 구한 면적들의 평균값과 최대값을 구하는 프로그램으로 인터페이스를 이용하여 작성해 보겠습니다.
[code php;gutter:false] /** * 도형 면적값을 획득하는 인터페이스 */ interface figure_area { /*abstract public*/ /*double*/ function get_area(); } [/code]
< 인터페이스 figure_area >
figure_area 인터페이스는 단 하나의 추상메쏘드 get_area로 구성되어 있으며, 어떤 도형인지는 알 수 없으나 그 면적값을 획득할 목적으로 작성된 것입니다. 이 인터페이스를 이용하여 삼각형, 사각형 및 원의 면적을 계산하고 계산된 값을 획득하는 각각의 클래스도 만들 것입니다.
figure_area 인터페이스만 보면 어떤 도형의 면적값을 획득하는 것인지를 알 수 없으나 이러한 미지의 도형 면적값을 추상메쏘드 get_area()를 통해 여러개를 입력받아 그 모든 값의 합과 평균값을 구하고 입력된 값 중에 최대값을 구하는 calculate 클래스를 작성해 보겠습니다.  
[code php;gutter:false] /** * 합, 평균값, 최대값을 구하는 클래스 */ class calculate { /*double*/ private $sum = 0; /*figure_area*/ private $maximum; /*integer*/ private $count = 0; public function add(/*figure_area*/ $x) { if ($x instanceof figure_area) { $this->sum += $x->get_area(); if ($this->count == 0 || $this->maximum->get_area() < $x->get_area()) { $this->maximum = $x; } $this->count++; } else { die("The variable x is not instance of figure_area\n"); } } public /*figure_area*/ function get_maximum() { return $this->maximum; } public /*double*/ function get_average() { return $this->sum / $this->count; } } [/code]
< 인터페이스 calculate >
PHP에서는 데이터형을 지정하지 않으나 이 예제에서는 소스의 정확한 의미를 전달하기 위하여 각 멤버 및 각 함수 입출력값의 데이터형을 주석으로 표기하였습니다.
calculate 클래스에 정의된 add 메쏘드의 입력값인 $x의 데이터형(data type)은 figure_area 인터페이스입니다.
10번행에 의해 $x가 figure_area 인터페이스의 멤버인지 확인한 후 멤버가 아니라면 20번행에 의해 에러메시지를 나타낸 후 즉시 스크립트를 종료합니다.
$x가 figure_area 인터페이스의 멤버인 경우에만 합과 최대값을 구하고 카운팅한 후  add 메쏘드 실행를 마칩니다. 13번행부터 16번행까지의 소스를 살펴보면 최대값을 기록하는 멤버 $maximum에는 면적의 실제 최대값이 기록되는 것이 아니라 최대값을 가지고 있는 figure_area형의 객체를 기록하고 있습니다.
24번행에 정의된 get_maximum 메쏘드는 add 메쏘드를 통해 입력된 여러개의 figure_area형 객체 중에서 최대면적값을 가지고 있던 객체를 반환해 줍니다. 따라서 get_maximum 메쏘드의 반환되는 값의 데이터형은 figure_area입니다.
28번행에 정의된 get_average 메쏘드는 입력된 값의 합을 입력된 값의 수로 나눈 평균값을 반환합니다.
마지막으로 figure_area  인터페이스를 구현해줄 각 도형에 대한 클래스를 작성하겠습니다.
[code php;gutter:false] /** * 삼각형 면적을 계산하고 획득하는 클래스 */ class triangle implements figure_area { private /*double*/ $width; private /*double*/ $height; function __construct(/*double*/ $width, /*double*/ $height) { $this->width = $width; $this->height = $height; } public /*double*/ function get_area() { return ($this->width * $this->height) / 2; } } /** * 사각형 면적을 계산하고 획득하는 클래스 */ class rectangle implements figure_area { private /*double*/ $width; private /*double*/ $height; function __construct(/*double*/ $width, /*double*/ $height) { $this->width = $width; $this->height = $height; } public /*double*/ function get_area() { return $this->width * $this->height; } } /** * 원의 면적을 계산하고 획득하는 클래스 */ class circle implements figure_area { const /*double*/ pi = 3.1415927; private /*double*/ $radius; function __construct(/*double*/ $radius) { $this->radius = $radius; } public /*double*/ function get_area() { return self::pi * ($this->radius ^ 2); } } [/code]
< 각 도형의 면적을 계산하는 클래스 >
각 도형의 제원(가로, 세로 또는 반지름)은 아래의 예와 같이 생성자를 통해 전달됩니다.
[code php;gutter:false] $tri = new triangle(4, 5); $rect = new rectangle(4, 5); $circle = new circle(4); [/code]
삼각형과 사각형은 가로와 세로길이를 입력값으로, 원은 반지름을 입력값으로 지정합니다.
클래스 triangle, rectangle, circle의 생성자를 통해 각 도형의 제원이 전달되면 이 값이 각 멤버($width, $height, $radius)에 기록되며 get_area 메쏘드에서는 기록된 각각의 제원(가로, 세로 또는 반지름)을 가지고 각 도형에 맞는 면적을 계산하여 반환합니다.
이에 대한 실행 소스는 아래와 같습니다.
[code php;gutter:false] $obj_triangle = new calculate(); $obj_triangle->add(new triangle(4, 5)); $obj_triangle->add(new triangle(2, 6)); $obj_triangle->add(new triangle(5, 3)); print "==========================\n"; print "삼각형의 면적 통계\n"; print "--------------------------\n"; print '평균면적 =' . $obj_triangle->get_average() . "\n"; $max = $obj_triangle->get_maximum(); print '최대면적 =' . $max->get_area() . "\n"; print "==========================\n"; [/code]
< 여러개의 삼각형 면적의 평균값과 최대값 구하기 >
3행에서 "new triangle(4, 5)"에 의해 triangle 클래스의 객체가 생성되는 동시에 그 생성자를 통해 입력된 가로, 세로값인 4와 5가 triangle 클래스의 멤버변수 $width와 $height에 기록됩니다. 그리고 생성된 인스턴스는 바로 calculate 클래스로부터 생성된 $obj_triangle 인스턴스의 add 메쏘드의 입력값으로 전달됩니다. add 메쏘드에서 전달받은 인스턴스는 figure_area 인터페이스를 구현한 삼각형 클래스의 인스턴스가 됩니다.
결국 위 소스를 실행하게 되면 아래와 같은 결과를 얻을 수 있습니다.
==========================
삼각형의 면적 통계
--------------------------
평균면적 =7.83333333333
최대면적 =10
==========================
calculate 클래스가 오로지 figure_area 인터페이스만을 의존하여 작성하였지만 실제로 calculate 클래스의 add 메쏘드에는 figure_area 인터페이스를 구현한 각 도형 클래스의 인스턴스가 전달되므로 각 도형의 면적에 대한 평균값과 최대값을 구할 수 있게 됩니다.
figure_area 인터페이스를 구현하고 있는 각 도형의 클래스와 calculate 클래스를 UML(Unified Modeling Language)로 나타내면 아래와 같습니다(UML에 관한 것은 관련 서적을 살펴보기 바랍니다).
< figure_area 인터페이스 >
이 예제에 대한 전체소스와 실행결과를 아래에 첨부합니다.
[code php;gutter:false] <?php /** * 도형 면적값을 획득하는 인터페이스 */ interface figure_area { /*abstract public*/ /*double*/ function get_area(); } /** * 합, 평균값, 최대값을 구하는 클래스 */ class calculate { /*double*/ private $sum = 0; /*figure_area*/ private $maximum; /*integer*/ private $count = 0; public function add(/*figure_area*/ $x) { if ($x instanceof figure_area) { $this->sum += $x->get_area(); if ($this->count == 0 || $this->maximum->get_area() < $x->get_area()) { $this->maximum = $x; } $this->count++; } else { die("The variable x is not instance of figure_area\n"); } } public /*figure_area*/ function get_maximum() { return $this->maximum; } public /*double*/ function get_average() { return $this->sum / $this->count; } } /** * 삼각형 면적을 계산하고 획득하는 클래스 */ class triangle implements figure_area { private /*double*/ $width; private /*double*/ $height; function __construct(/*double*/ $width, /*double*/ $height) { $this->width = $width; $this->height = $height; } public /*double*/ function get_area() { return ($this->width * $this->height) / 2; } } /** * 사각형 면적을 계산하고 획득하는 클래스 */ class rectangle implements figure_area { private /*double*/ $width; private /*double*/ $height; function __construct(/*double*/ $width, /*double*/ $height) { $this->width = $width; $this->height = $height; } public /*double*/ function get_area() { return $this->width * $this->height; } } /** * 원의 면적을 계산하고 획득하는 클래스 */ class circle implements figure_area { const /*double*/ pi = 3.1415927; private /*double*/ $radius; function __construct(/*double*/ $radius) { $this->radius = $radius; } public /*double*/ function get_area() { return self::pi * ($this->radius ^ 2); } } $obj_triangle = new calculate(); $obj_triangle->add(new triangle(4, 5)); $obj_triangle->add(new triangle(2, 6)); $obj_triangle->add(new triangle(5, 3)); print "==========================\n"; print "삼각형의 면적 통계\n"; print "--------------------------\n"; print '평균면적 =' . $obj_triangle->get_average() . "\n"; $max = $obj_triangle->get_maximum(); print '최대면적 =' . $max->get_area() . "\n"; $obj_rectangle = new calculate(); $obj_rectangle->add(new rectangle(4, 5)); $obj_rectangle->add(new rectangle(2, 6)); $obj_rectangle->add(new rectangle(5, 3)); print "==========================\n"; print "사각형의 면적 통계\n"; print "--------------------------\n"; print '평균면적 =' . $obj_rectangle->get_average() . "\n"; $max = $obj_rectangle->get_maximum(); print '최대면적 =' . $max->get_area() . "\n"; $obj_circle = new calculate(); $obj_circle->add(new circle(4)); $obj_circle->add(new circle(2)); $obj_circle->add(new circle(5)); print "==========================\n"; print "원의 면적 통계\n"; print "--------------------------\n"; print '평균면적 =' . $obj_circle->get_average() . "\n"; $max = $obj_circle->get_maximum(); print '최대면적 =' . $max->get_area() . "\n"; print "==========================\n"; ?> [/code]
< 여러 도형 면적의 평균값과 최대값 구하기 >
==========================
삼각형의 면적 통계
--------------------------
평균면적 =7.83333333333
최대면적 =10
==========================
사각형의 면적 통계
--------------------------
평균면적 =15.6666666667
최대면적 =20
==========================
원의 면적 통계
--------------------------
평균면적 =13.6135683667
최대면적 =21.9911489
==========================
< 예제 실행 결과 >

Posted by 방글24
phpclass/객체모델2005. 10. 11. 12:23
written: Nov 07 2007
정적 멤버(static member)
PHP5에서 정적 멤버를 static 키워드를 이용하여 정의하여 사용할 수 있습니다.
visibility static $변수이름 = 변수값;
visibility static function 함수이름(인수리스트) { 함수내용 };
visibility 위치에는 정적 멤버의 가시범위(visibility)를 제한할 수 있는 PPP 접근제한자(private/protected/public access modifier)를 지정할 수 있습니다. 생략하면 PHP4와의 호환성을 고려하여 멤버를 public로 처리합니다.
정적 멤버는 객체를 생성하지 않고도 접근할 수 있도록 해줍니다.
[code php;gutter:false] <?php class Foo { public static $my_static = 'foo'; public function staticValue() { return self::$my_static; } } class Bar extends Foo { public function fooStatic() { return parent::$my_static; } } print Foo::$my_static . "\n"; $foo = new Foo(); print $foo->staticValue() . "\n"; print Bar::$my_static . "\n"; $bar = new Bar(); print $bar->fooStatic() . "\n"; ?> [/code]
< 예제출처:php.net >
정적 멤버 변수(static member variable)
[code php;gutter:true] <?php class test_class { public static $static_var = "my static variable's value\n"; public function get_static() { return self::$static_var; } public function set_static($val) { self::$static_var = $val . "\n"; } } print test_class::$static_var; $obj = new test_class(); $obj->set_static("my static variable's new value"); $obj2 = new test_class(); print $obj2->get_static(); ?> [/code]
위에서 볼 수 있듯이 정적 멤버 변수로 선언된 변수는 어느 인스턴스에서 수정되어도 모든 인스턴스에 적용이 됩니다. 위에서 17번째 줄에 보이듯이 $obj 인스턴스에 의해 수정된 정적 멤버 변수는 $obj2의 다른 인스턴스에도 모두 영향을 미칩니다.
출력 결과는 아래와 같습니다.
my static variable's value
my static variable's new value
클래스 상수와 마찬가지로 정적 멤버 변수는 객체를 통해서 접근할 수 없습니다. 이것은 정적 멤버 함수가 객체를 통해서 접근할 수 있는 것과 다르다는 점에 주의해야 합니다. 따라서 클래스 내부에서만 사용되는 $this 키워드를 이용해서도 접근이 불가능하며 반드시 범위지정연산자(::) 앞에 클래스명, self 또는 parent 키워드를 이용해서 접근해야만 합니다.
[code php;gutter:true] <?php class test_class { public static $static_var = "my static variable's value\n"; } $obj = new test_class(); print $obj->static_var; // Notice: Undefined property: test_class::$static_var in xxx.php on line 8 // $obj::static_var is not possible // Parse error: parse error, unexpected T_PAAMAYIM_NEKUDOTAYIM // in xxx.php on line nnn ?> [/code]
정적 멤버 함수(static method)
정적 멤버 함수는 객체를 생성하지 않고도 접근할 수 있도록 해줍니다.
[code php;gutter:false] <?php class my_class { public static function hello_world() { print "Hello, world"; } } my_class::hello_world(); ?> [/code]
생성된 객체를 통해서 멤버에 접근할 수 있습니다.
[code php;gutter:false] <?php class my_class { public static function hello_world() { print "Hello, world"; } } $obj = new my_class; $obj->hello_world(); ?> [/code]
그러나 객체 생성없이 접근하는 경우도 있기 때문에 의사 변수(pseudo variable) $this를 정적 멤버 함수 내에서 사용해서는 안됩니다.
[code php;gutter:false] <?php class my_class { public static function hello_world() { print "Hello, world"; } public static function hello() { $this->hello_world(); } } $obj = new my_class; $obj->hello(); // Fatal error: Using $this when not in object context in xxx.php on line 8 ?> [/code]
정적멤버가 아닌 메쏘드를 정적으로 호출하는 것은 E_STRICT 레벨의 경고를 발생시킵니다.
[code php;gutter:false] <?php class my_class { public function hello_world() { print "Hello, world"; } } error_reporting(E_ALL | (defined('E_STRICT')? E_STRICT : 0)); my_class::hello_world(); // Strict Standards: Non-static method my_class::hello_public() // should not be called statically in xxx.php on line 10 ?> [/code]
싱글턴 패턴(singleton pattern)
정적 멤버 변수를 가장 유용하게 활용하는 곳이 있다면 디자인 패턴 중 싱글턴 패턴(singleton pattern)일 것입니다.
싱글턴 패턴은 프로그램이 동작할 때 클래스의 인스턴스가 반드시 하나만 존재하도록 해주는 패턴으로 매우 중요한 요소입니다만 PHP4에서는 정적멤버, PPP 접근제한자, 인터페이스 등의 객체지향언어의 핵심기능을 빠진 상태에서 구현이 사실상 불가능하였습니다.
PHP5부터 제공되기 시작한 정적멤버 및 PPP 접근제한자 등의 특성을 이용하여 디자인 패턴 중에서도 가장 널리 사용되는 싱글턴 패턴을 구현해 보겠습니다. 소스는 php.net의 document에서 발췌하여 수정한 것입니다.
[code php;gutter:false] <?php class Example { // Hold an instance of the class private static $instance; // Prevents direct creation of object private function __construct() { echo 'I am constructed'; } // The singleton method public static function singleton() { if (!isset(self::$instance)) { $c = __CLASS__; self::$instance = new $c; } return self::$instance; } // Example method public function bark() { echo 'Woof!'; } // Prevent users to clone the instance private function __clone() { } } //This allows a single instance of the Example class to be retrieved. // This will always retrieve a single instance of the class $test = Example::singleton(); $test->bark(); ?> [/code]
클래스 밖에서 singleton() 메쏘드를 이용하지 않고 아래 코드와 같이 new 또는 clone 연산자를 이용하여 객체를 생성하려면 Fatal 에러가 발생하기 때문에 프로그램상에서 인스턴스는 단 1개만 존재할 수 있습니다.
[code php;gutter:false] <?php class Example { // Hold an instance of the class private static $instance; // Prevents direct creation of object private function __construct() { echo 'I am constructed'; } // The singleton method public static function singleton() { if (!isset(self::$instance)) { $c = __CLASS__; self::$instance = new $c; } return self::$instance; } // Example method public function bark() { echo 'Woof!'; } // Prevent users to clone the instance private function __clone() { } } // This would fail because the constructor is private $test = new Example; // Fatal error: Call to private Example::__construct() // from invalid context in xxx.php on line 30 // This will issue an E_USER_ERROR. $test_clone = clone $test; // Fatal error: Call to private Example::__clone() from context '' // in xxx.php on line 35 ?> [/code]
다중쓰레드(multi thread)를 지원하지 않는 PHP에서야 별 상관없는 이야기이지만 자바와 같이 다중쓰레드를 지원하는 프로그램에서는 위의 코드도 완벽하지 않습니다. 여러 개의 쓰레드가 거의 동시에 singleton() 메쏘드를 호출한다고 가정한다면 순간적으로 여러 개의 인스턴스가 생성될 수 있습니다. 따라서 singleton() 메쏘드는 단 하나의 쓰레드만 접근할 수 있도록 제한할 필요가 있습니다. 이를 위한 연산자가 synchronized입니다. 다음은 "Java 언어로 배우는 디자인 패턴 입문 - (주)영진닷컴" p.445에 나오는 synchronized 연산자를 적용한 싱글턴 페턴 클래스 예제입니다.
[code java;gutter:false] public class Singleton { private static Singleton singleton = null; private Singleton() { System.out.println("인스턴스를 생성했습니다"); slowdown(); } public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } private void slowdown() { try { Thread.sleep(1000); } catch (InterruptedException e) { } } } [/code]
위 소스에서 slowdown() 메쏘드는 다중쓰레드에 의해 여러 개의 인스턴스가 생성되는지 확인하기 위해 프로그램의 동작 속도를 일부로 느리게 하기 위해 첨가된 코드입니다.

Posted by 방글24
phpclass/객체모델2005. 10. 11. 12:21
written: Oct 18 2005
상수
상수(constant)라는 것은 프로그램에서 한번 값이 결정되면 다시는 새로운 값으로 변경될 수 없는 객체입니다. 언어마다 상수의 분류가 다소 다르기는 합니다만 대체적으로 숫자형, 문자형, 문자열 상수로 구분되며 자바에서는 부울린형이 추가되어 있습니다. 참고로 자바에서는 상수를 리터럴(literal)이라고 부릅니다.
< 상수의 분류 >
대분류 소분류 사용예
숫자형 정수 1, 100, 2005, 0777, 0xFF, ...
실수 1.2, 100.0, 3.1415927, ...
지수 0.12e-2, 10e45, 0.36E-3, ...
문자형 영문자 소문자 'a' - 'z', 대문자 'A' - 'Z'
숫자형문자 '1'  - ' 9'
특수문자 '#', '$', '?', newline과 같은 escope code, ...
부울린형 예약어 true, false
문자열형 "Hello world.", ...
상수를 변수처럼 사용하는 방법
각 언어마다 상수를 변수처럼 이름을 가진 상수로 사용할 수 있도록 해줍니다. 이 방법은 언어마다, 그리고 상수가 정의된 위치에 따라 다를 수 있기때문에 각 언어별(PHP, C++, 자바)로 상수이름을 어떻게 정의하여 사용할 수 있는지 살펴보겠습니다.
PHP에서는 5.0부터 클래스에서 정의되는 상수를 제공합니다. 이 상수를 클래스 상수라고 하며 기존의 전역영역에서 사용하던 전역상수와 구별하여 사용할 수 있습니다.
PHP에서 전역상수를 정의하기 위해서는 define 함수를 이용합니다.
[code php;gutter:false] <?php define("PI", 3.1415927); echo PI; ?> [/code]
C++에서는 아래와 같은 방법으로 상수의 이름을 사용하도록 해줍니다.
[code c;gutter:false] #define PI 3.1415927 [/code]
위와 같은 C++에서의 방법은 PHP의 define() 함수에 의한 방법과는 그 구현방법이 다릅니다. C++에서의 #define은 프리프로세서(preprocessor)로 매크로 기능을 이용하는 방법으로 컴파일하기 전에 상수이름이 해당 상수로 모두 대치됩니다.
PHP의 define() 함수에 의해 정의된 상수는 하나의 기억장소에 저장되나 그 값이 변수처럼 새로운 값으로 치환되지 못하도록 합니다.
아래와 같이 C++에서의 또 다른 상수이름을 지정할 수 있는 방법인 const 키워드를 이용하는 방법이 있으며 이 방법의 의해 정의된 상수이름은 PHP의 define() 함수에 의해 정의된 함수처럼 하나의 기억장소를 할당받으며 그 값을 변경할 수 없습니다.
[code c;gutter:false] const float pi = 3.1415927; [/code]
PHP5에서 제공되는 클래스 상수가 위와 유사하게 const 키워드를 이용하여 정의합니다.
[code php;gutter:false] const pi = 3.1415927; [/code]
PHP에서는 float와 같은 자료형을 지정하지 않는다는 것을 감안하면 C++ 표현 방식과 같다는 것을 알 수 있습니다.
참고로 자바에서는 #define, const 대신에 final 키워드를 사용하여 상수(리터럴) 이름을 지정할 수 있습니다.
[code java;gutter:false] final float pi = 3.1415927; [/code]
클래스 상수
클래스 상수는 클래스 단위로 설정할 수 있는 상수로 아래와 같이 const 키워드를 사용하여 클래스 상수를 정의하며 전역 상수와 마찬가지로 변수를 나타내는 $ 문자를 붙이지 않습니다.
const 클래스상수명 = '상수값';
클래스 상수는 클래스 안에서 사용되는 키워드 $this뿐만 아니라 생성된 인스턴스를 가지고는 접근할 수 없습니다. 따라서 클래스 상수를 사용하기 위해서는 아래와 같이 self, parent 키워드 또는 클래스명을 통해 접근해야 합니다.
[code php;gutter:false] <?php class super_class { const constant = 'super_class::constant value'; function show_constant() { print self::constant . "\n"; } } class sub_class extends super_class { const constant = 'sub_class::constant value'; function show_constant() { print parent::constant . "\n"; print self::constant . "\n"; } } /** * This will print * super_class::constant value */ print super_class::constant . "\n"; /** * This will print * sub_class::constant value */ print sub_class::constant . "\n"; $obj = new sub_class; /** * This will print * super_class::constant value * sub_class::constant value */ $obj->show_constant(); /** * This will print * super_class::constant value * sub_class::constant value */ sub_class::show_constant(); /** * This will print * super_class::constant value */ super_class::show_constant(); /** * echo $obj::constant; is not allowed */ ?> [/code]
< 클래스 상수 사용하기 >
클래스 상수는 항상 public 가시범위(visibility)를 가지고 있기 때문에 const 키워드 앞에는 public 접근 제한자를 포함하여 어떠한 접근제한자도 지정해서는 안됩니다.

Posted by 방글24
phpclass/객체모델2005. 10. 11. 12:17
written: Oct 12 2005
추상클래스는 객체가 가지는 특성들을 추상화시켜 놓았을 뿐 아직 인스턴스화될 수 없는 클래스입니다. "상단메뉴 >> 클래스&객체 > 추상클래스"를 참조하셔서 먼저 추상클래스가 무엇인지를 이해하시기 바랍니다.
추상클래스(abstract class)
[code php;gutter:false] <?php /** * 동물(animal) 추상클래스 */ abstract class animal { abstract protected function is_body_size(); public function display($kind) { print $kind . '의 몸집은 ' . $this->is_body_size() . ".\n"; } } /** * 사자(lion) 클래스 */ class lion extends animal { protected function is_body_size() { return '크다'; } } /** * 거북이(turtle) 클래스 */ class turtle extends animal { protected function is_body_size() { return '작다'; } } $obj = new lion; $obj->display('사자'); $obj = new turtle; $obj->display('거북이'); ?> [/code]
위의 예제의 출력결과는 아래와 같습니다.
사자의 몸집은 크다.
거북이의 몸집은 작다.
lion(사자)나 turtle(거북이)라는 것이 실제로 존재하는 것이라면 animal(동물)이라는 것은 사자, 거북이 등의 특성을 가진 생물을 모두 지칭하는 추상적인 용어일 것입니다. 위의 예에서 보다시피 각 동물마다 몸집크기는 제각각일 것이고 따라서 추상클래스에서는 몸집크기를 알아내는 메쏘드 is_body_size()를 실제적으로 구현할 수 없습니다.
이와 같이 추상클래스라는 것이 실제로 객체로 생성할 수 있도록 구현된 것이 아니라 구현하고자 하는 객체들이 가지는 특성들을 추상화시켜 놓은 것입니다. 따라서 각 객체가 가지는 구체적인 특성들은 하위클래스에서 재정의(overriding)를 통해 구현해 주어야 합니다.
추상클래스라고해서 모든 멤버가 추상적이어야 하는 것은 아닙니다. 때에 따라서는 추상클래스에서도 일반클래스와 마찬가지로 실제로 구현된 멤버를 가질 수 있습니다. 동물(animal)이라는 추상적인 개념에서도 "움직인다"와 같이 실제로 구현될 수 있는 특성을 포함할 수 있는 것과 같다고 볼 수 있습니다.
[code php;gutter:false] <?php abstract class animal { abstract protected function is_body_size(); protected $action = '움직인다'; public function display($kind, $speed) { print $kind . '의 몸집은 ' . $this->is_body_size() . ".\n"; print $kind . '는 ' . $speed . ' ' . $this->action . ".\n"; } } class lion extends animal { protected function is_body_size() { return '크다'; } } class turtle extends animal { protected function is_body_size() { return '작다'; } } $obj = new lion; $obj->display('사자', '빠르게'); $obj = new turtle; $obj->display('거북이', '느리게'); ?> [/code]
위의 예제의 출력결과는 아래와 같습니다.
사자의 몸집은 크다.
사자는 빠르게 움직인다.
거북이의 몸집은 작다.
거북이는 느리게 움직인다.
위의 예에서 보시다시피 추상클래스라는 것이 그 자체로는 실제로 할 수 있는 일이 없습니다. 그러나 상속과 재정의를 통해서 매우 쓸모있는 것으로 바꿀 수 있습니다.
추상메쏘드(abstract method)
추상메쏘드는 아래의 예와 같이 함수의 몸체가 없고 단지 프로토타입만 선언해주는 메쏘드입니다.
[code php;gutter:false] abstract protected function is_body_size(); [/code]
이러한 추상메쏘드는 추상메쏘드를 포함하고 있는 클래스를 상속한 하위클래스에서 그 몸체를 실제적으로 구현해 주어야  일반 클래스처럼 객체를 생성하여 사용할 수 있습니다.
[code php;gutter:true] <?php abstract class animal { abstract protected function is_body_size(); protected static $kind = '동물'; public function display() { print self::$kind . "의 몸집은 그 종류에 따라 다릅니다.\n"; } } $obj = new animal; /* Fatal error 발생 */ $obj->display(); ?> [/code]
위와같이 추상클래스로 객체를 생성하려고 시도하면 아래와 같이 11번행에서 Fatal error가 나타납니다.
Fatal error: Cannot instantiate abstract class animal
추상메쏘드는 추상클래스 안에서만 존재할 수 있기때문에 클래스 안에 하나 이상의 추상메쏘드가 포함되어 있으면 그 클래스는 반드시 추상클래스로 지정해 주어야 합니다.
[code php;gutter:true] <?php class animal { abstract protected function is_body_size(); } /* Fatal error 발생 */ ?> [/code]
위와같이 클래스를 abstract로 지정하지 않고 추상메쏘드를 포함시키면 아래와 같이 4번행에서 Fatal error가 나타납니다.
Fatal error: Class animal contains 1 abstract methods and must therefore be declared abstract (animal::is_body_size)
추상메쏘드의 가시범위(visibility)
추상메쏘드는 하위클래스에서 반드시 구현해주어야 하기 때문에 protected 또는 public으로 지정해 주어야 하며 private로 지정할 수 없습니다.
< 추상메쏘드의 PPP 지정 및 상속 >
접근제한자 private protected public

추상메쏘드에 지정할 수 있는

접근제한자

지정할 수 없음 O O

하위클래스의 재정의된 메쏘드에서

지정할 수 있는 접근제한자

N/A

protected

public

public
 (NA:Non Applicable;해당없음).....
추상클래스의 상속
상위추상클래스를 상속받은 하위클래스에서는 상위추상클래스의 추상메쏘드를 모두 구현할 수도 있고, 새로운 추상메쏘드를 추가할 수도 있고, 상위추상클래스에 있는 일부 추상메쏘드를 구현하지 않고 계속 남겨둘 수도 있습니다. 하위클래스에 일부 추상메쏘드가 구현되지 않고 계속 남아있는 경우에 이 하위클래스도 역시 추상클래스이므로 반드시 abstract로 지정해 주어야 하며 역시 객체를 생성할 수 없습니다.
[code php;gutter:true] <?php abstract class animal { abstract protected function is_body_size(); abstract protected function is_speed(); public function display($kind) { print $kind . '의 몸집은 ' . $this->is_body_size() . ".\n"; } } class lion extends animal { protected function is_body_size() { return '크다'; } } $obj = new lion; /* Fatal error 발생 */ $obj->display('사자'); ?> [/code]
위와같이 상위추상클래스에 있는 추상메쏘드를 모두 구현하지 않은 하위클래스를 가지고 객체를 생성하려고 시도하면 아래와 같이 17번행에서 Fatal error가 나타납니다.
Fatal error: Cannot instantiate abstract class animal
위와 같은 경우에 사자(lion) 클래스를 아래와 같이 추상클래스로 수정해야 하며 상속된 하위추상클래스를 상속한 그 하위클래스에서 나머지 추상메쏘드를 모두 구현하고 난 다음에 객체를 생성할 수 있습니다.
[code php;gutter:false] <?php abstract class lion extends animal { protected function is_body_size() { return '크다'; } } class concreat_lion extends lion { protected function is_speed() { return '빠르다'; } } $obj = new concreat_lion; $obj->display('사자'); ?> [/code]

Posted by 방글24
phpclass/객체모델2005. 10. 11. 12:13
written: Oct 06 2005
last modified: Oct 11 2005
 
앞 문서들에서 접근제한자를 이용하여 가시범위(visibility) 지정하는 방법에 대하여 알아보았습니다. 이번 문서에서는 범위 지정 연산자(scope resolution operator;범위지정자)를 이용하여 유효범위(scope)를 지정하는 방법에 대하여 살펴보겠습니다.
유효범위(scope)
유효범위라는 것은 하나의 멤버를 기준으로 그 멤버에 접근할 수 있는 프로그램 영역을 나타내는 것으로 만일 프로그램의 모든 곳에서 접근할 수 있는 멤버라면 그 멤버의 유효범위는 프로그램 전체가 됩니다. 모든 멤버(변수, 함수, 상수)의 유효범위(scope)는 그 멤버가 정의되어 있는 프로그램의 위치에 따라 결정됩니다.
zend 1.0과 2.0의 유효범위에 관한 자세한 내용은  "상단메뉴 >> 클래스&객체 > Zend엔진 2.0 설계초안 > 이름공간(namespace)"를 참조바랍니다.
위 문서에 따르면 Zend 엔진에서는 아래와 같이 3가지의 유효범위를 제공하는 것으로 보입니다.
  • 전역 스코프(golbal scope)
  • 함수 스코프(function scope)
  • 클래스 스코프(class scope)
본 문서에서는 유효범위를 구분할 때에는 유효범위라는 용어보다는 스코프라는 용어를 직접 사용하여 가능한한 용어 사용상 혼동이 없도록 하겠습니다.
위에서 구분된 함수 스코프(function scope)라는 용어를 C++과 같은 언어에서는 지역 스코프(local scope)라고 부르고 있으며 함수 스코프는 다른 뜻으로 사용하고 있으니 양쪽 언어를 넘나드는 분들은 혼동없기를 바랍니다.
참고로 C++에서는 지역 스코프를 아래와 같이 설명하고 있습니다.
함수 또는 함수에 있는 어떤 블록(block) 안에서 정의되어 있는 모든 변수들은 지역 스코프를 가진다.
PHP에서는 블록 안에서만 사용할 수 있는 변수를 허용하지 않고 다만 함수 내에서 사용할 수 있는 변수를 허용하고 있습니다.
클래스 스코프(class scope)
Zend 엔진 2.0 알파2가 발표되면서 클래스 스코프와 관련하여 큰 변화는 중첩클래스(nested class;내부클래스)를 허용한다는 것과 import 문의 도입이었을 것입니다만 PHP5.0.0b1 이후 현재버전(5.0.4)에서는 둘 다 삭제되어 있습니다. 클래스 스코프에 포함될 수 있는 멤버를 보면 아래와 같습니다.
< 클래스 스코프 (o:포함허용, x:포함불가) >

\

Zend 엔진 버전

------------------

 멤버

\

1.0 2.0 알파2

2.0

상수

X

O

O

변수

O

O

O

함수

O

O

O

클래스

X

O

X

범위 지정 연산자(::)
앞에서 모든 멤버는 그 멤버가 정의된 프로그램 위치에 따라 그 멤버의 유효범위가 결정된다고 하였습니다. 그런데 때로는 자신이 속한 유효범위를 명시적으로 지정하여 사용할 수도 있습니다. 이러한 목적으로 범위 지정 연산자(scope resolution operator;범위지정자)를 사용합니다.
범위 지정 연산자를 이용하면 부모클래스 내에 있는 재정의되기 전의 멤버 또는 아직 인스턴스를 갖고 있지 않는 클래스 멤버에 접근할 수 있습니다.
클래스 밖에서 클래스 멤버 접근하기
클래스 밖에서 클래스 멤버에 접근하기 위해서는 "클래스명::멤버"와 같이 지정합니다.
상수(constant)
[code php;gutter:false] <?php class my_class { const MY_CONST = 'A constant value.'; } print my_class::MY_CONST . "\n"; ?> [/code]
< 상수에 접근하기 >
위 소스를 실행하면 "A constant value.\n"를 출력할 것입니다.
변수(variable)
[code php;gutter:false] <?php class my_class { public static $public_var = "my_class::public_var"; } print my_class::$public_var . "\n"; ?> [/code]
< 변수에 접근하기 >
위 소스를 실행하면 "my_class::public_var\n"를 출력할 것입니다.
함수(function)
[code php;gutter:false] <?php class my_class { public function my_function() { print "my_class::my_function()\n"; } } class my_class2 extends my_class { public function my_function() { my_class::my_function(); print "my_class2::my_function()\n"; } } /** * This will print * my_class::my_function() */ my_class::my_function(); /** * This will print * my_class::my_function() * my_class2::my_function() */ ?> [/code]
< 함수에 접근하기 >
클래스 안에서 클래스 멤버 접근하기
클래스 안에서 클래스 멤버에 접근할 때는  "클래스명::멤버"와 같이 지정할 수도 있으나 PHP4부터 제공되기 시작한 self와 parent 접근자를 이용하여 자신의 클래스나 상위 클래스의 멤버에 접근하는 것이 더 바람직합니다.
상수(constant)
[code php;gutter:false] <?php class my_class { const MY_CONST = "my_class::MY_CONST\n"; protected function my_function() { print self::MY_CONST; } } class my_class2 extends my_class { const MY_CONST = "my_class2::MY_CONST\n"; public function my_function() { print parent::MY_CONST; print self::MY_CONST; } } /** * This will print * my_class::MY_CONST */ my_class::my_function(); /** * This will print * my_class::MY_CONST * my_class2::MY_CONST */ my_class2::my_function(); ?> [/code]
< 상수에 접근하기 >
변수(variable)
[code php;gutter:false] <?php class my_class { public static $protected_var1 = "my_class::protected_var1\n"; public function my_function() { print self::$protected_var1; } } class my_class2 extends my_class { protected static $protected_var2 = "my_class2::protected_var2\n"; public function my_function() { print parent::$protected_var1; print self::$protected_var2; } } /** * This will print * my_class::protected_var1 */ my_class::my_function(); /** * This will print * my_class::protected_var1 * my_class2::protected_var2 */ my_class2::my_function(); ?> [/code]
< 변수에 접근하기 >
(에러수정) 예제가 잘못 코딩되어 에러가 발생하여 예제를 수정하였습니다.
함수(function)
[code php;gutter:false] <?php class my_class { protected function my_function() { print "my_class::my_function()\n"; } } class my_class2 extends my_class { protected function my_function() { print "my_class2::my_function()\n"; } public function public_func() { print parent::my_function(); print self::my_function(); } } my_class2::public_func(); ?> [/code]
< 함수에 접근하기 >
위 소스를 실행하면 아래와 같은 결과를 얻을 수 있습니다.
my_class's my_function()
my_class2's my_function()
심볼테이블
"상단메뉴 >> 클래스&객체 > Zend엔진 2.0 설계초안 > 이름공간(namespace)"를 보게되면 Zend엔진 각 버전별로 클래스의 심볼(상수명, 변수명, 함수명, 클래스명 등)에 대한 기본적인 유효범위가 상이합니다.
[code php;gutter:true] <?php function bar() { print "global's bar()\n"; } class my_class { function my_function() { bar(); } function bar() { print "myclass::bar()\n"; } } ?> [/code]
< 함수의 유효범위 >
위의 예에서 8번행의 bar()가 실행되었을 때 Zend 엔진 1.0에서는 전역 스코프(global scope)를 가지게 되어 전역함수 bar()를 호출하게 되며 결과는 "global's bar()\n"를 출력할 것입니다. 그러나 Zend 엔진 2.0 알파2에서는 8번행의 bar()가 실행되었을 때 bar() 문이 클래스 스코프(class scope)를 가지게 되어 클래스 메쏘드인 bar()를 호출하게 되며 결과는 "myclass::bar()\n"를 출력할 것입니다.
그런데 PHP5.0이 정식으로 발표되면서 포함된 Zend 엔진 2.0에서는 원래대로 클래스 스코프가 아니라 전역 스코프를 가지도록 수정되었습니다.
< 8번행 bar()문의 유효범위 >

\

Zend 엔진 버전

------------------

 유효범위

\

1.0 2.0 알파2

2.0

전역 스코프

O

X

O

클래스 스코프

X

O

X

main 접근자
Zend 엔진 2.0 알파2에서 제공되던 범위 접근자인 main이 PHP5.0.0b1이 발표되면서 삭제되었습니다. 그 이유는 앞 항목인 "심볼테이블"에서 살펴보았지만  bar()문을 실행하였을 때 Zend 엔진 2.0 알파2에서는 클래스 my_class에 정의된 bar() 메쏘드를 호출하게 됩니다. 그러면 Zend 엔진 2.0 알파2에서 전역함수 bar() 함수를 호출하려면 어떻게 해야 하나요? 이럴 때를 위하여 제공하였던 것이 main 접근자입니다. main 접근자로 임의 지정된 멤버는 현재 위치에 관계없이 전역영역에 있는 멤버를 호출하도록 해줍니다.
main::bar();
PHP5.0.0b1 이후에 포함된 Zend 엔진 2.0에서는 bar()문을 실행하였을 때 Zend 엔진 1.0과 같이 클래스 메쏘드가 아닌 전역함수를 호출하도록 변경되었기 때문에 main 접근자가 필요없어졌습니다. 이러한 이유로 Zend 엔진 2.0에서는 main 접근자가 삭제되었습니다.

Posted by 방글24
phpclass/객체모델2005. 10. 11. 12:12
written: Sep 30 2005
last modified: Oct 11 2005
public 멤버
public 멤버는 프로그램의 어느 곳에서나 접근할 수 있습니다. 접근하는 곳이 전역영역일 수도 있고 다른 클래스의 멤버가 될 수도 있을 것입니다.
PHP4에서는 PPP 접근제한자를 허용하지 않았으며 변수는 var로, 메쏘드는 function으로만 선언할 수 있었습니다. 이렇게 작성된 멤버는 무조건 public 특성을 가지게 됩니다. PHP5에서도 var로 선언된 변수는 public 변수와 같으며, function 앞에 명시적으로 접근제한자를 붙이지 않으면 public 메쏘드로 처리합니다.

Posted by 방글24
phpclass/객체모델2005. 10. 11. 12:11
written: Sep 30 2005
last modified: Oct 11 2005
protected 멤버
protected 멤버는 해당 클래스 멤버뿐만 아니라 클래스를 상속받은 하위클래스에서도 상위클래스의 protected 멤버에 접근할 수 있습니다.
protected로 선언된 멤버의 경우에 C++이냐 자바냐에 따라 그 의미가 약간씩 다르기도 하고 추가되는 개념도 있습니다. 가장 큰 차이점은 C++에서는 바로 아래에 있는 하위 클래스에서만 접근할 수 있는 반면에 자바에서는 패키지라는 개념이 도입되어 패키지 안에 있는 어떤 클래스도 접근할 수 있게 해 줍니다. PHP5에서는 해당 클래스로부터 상속된 모든 하위클래스에서 접근할 수 있습니다.
해당 클래스에서는 protected 선언하나 private 멤버로 선언하나 그 동작이 동일합니다. 또한 다른 클래스나 전역영역에서 접근할 수 없다는 것도 동일합니다. 해당 클래스로부터 상속받은 하위클래스에서만 차이가 납니다. 상위클래스에서 private로 선언된 멤버는 접근할 수 없으나 protected로 선언된 멤버는 하위클래스에 상속되어 자유로이 접근할 수 있습니다.
[code php;gutter:false] <?php class my_class { protected $protected1 = "my_class::protected1!\n"; protected $protected2 = "my_class::protected2!\n"; protected function my_protected1() { return "my_class::my_protected1()!\n"; } protected function my_protected2() { return "my_class::my_protected2()!\n"; } function foo() { print "my_class::foo() " . $this->protected1; print "my_class::foo() " . $this->protected2; print "my_class::foo() " . $this->my_protected1(); print "my_class::foo() " . $this->my_protected2(); } } class my_class2 extends my_class { protected $protected1 = "my_class2::protected1!\n"; protected function my_protected1() { return "my_class2::my_protected1()!\n"; } function foo2() { print "my_class::foo2() " . $this->protected1; print "my_class::foo2() " . $this->protected2; print "my_class::foo2() " . $this->my_protected1(); print "my_class::foo2() " . $this->my_protected2(); } } $obj2 = new my_class2; /** * Will output * "my_class::foo2() my_class2::protected1!" * "my_class::foo2() my_class::protected2!" * "my_class::foo2() my_class2::my_protected1()!" * "my_class::foo2() my_class::my_protected2()!" */ $obj2->foo2(); ?> [/code]
< protected로 지정된 멤버의 동작 >

Posted by 방글24
phpclass/객체모델2005. 10. 11. 12:10
written: Sep 30 2005
last modified: Oct 11 2005
private 멤버
private 멤버는 해당 클래스내에서만 접근할 수 있습니다. 전역영역 및 다른 클래스는 물론이고 해당 클래스를 상속받은 하위클래스에서 조차 접근할 수 없는 가장 제한적인 방법입니다.
private 변수
[code php;gutter:true] class my_class { private $private = "my_class::private!\n; function foo() { print "my_class::foo() " . $this->private; } } $obj = new my_class; /** * Fatal error: Cannot access private property my_class::$private */ print $obj->private; $obj->foo(); [/code]
< private로 지정된 변수의 동작 >
14번행을 실행하면 아래와 같은 에러를 보게 됩니다.
Fatal error: Cannot access private property my_class::$private in xxx.php on line 14
private로 선언된 변수 $private는 전역 영역에서 접근할 수 없다는 것을 보여주는 것입니다. 반면에 16번행을 실행할 때는 별 탈(?)없이 잘 실행되지요. 클래스 my_class에 선언된 메쏘드 foo() 내에서는 같은 클래스에 선언된 private 변수 $private를 접근하는 데 아무런 문제가 없다는 것을 나타냅니다. 따라서 16번행을 실행하면 private 변수 $private의 값인 "my_class::private!\n이 정상적으로 출력됩니다.
다음에는 클래스 my_class를 상속한 하위클래스에서는 어떤 반응을 보이는지 살펴보겠습니다.
[code php;gutter:true] class my_class2 extends my_class { private $private = "my_class2::private!\n; function foo2() { print "my_class::foo2() " . $this->private; } } $obj2 = new my_class2; /** * Fatal error: Cannot access private property my_class2::$private */ print $obj2->private; $obj2->foo2(); [/code]
< 하위클래스에서 private로 지정된 변수의 동작 >
클래스 my_class에서 private로 선언된 $private를 하위클래스 my_class2에서 같은 변수명을 가지고 private로 선언해 보았습니다. 14번행을 실행하면 역시 아래와 같은 에러를 보게 됩니다.
Fatal error: Cannot access private property my_class2::$private in xxx.php on line 14
이전 에러메시지와 비교해 볼 때 달라진 점은 my_class::$private 대신에 my_class2::$private에 접근할 수 없다는 것입니다.. 만약에 하위클래스 my_class2에서 2번행을 삭제한다면 어떻게 될까요. 이 때는 아래와 같이 Notice 에러를 만나게 될 것입니다.
Notice: Undefined property: my_class2::$private in xxx.php on line 14
에러메시지를 보면 클래스 상위클래스인 my_class에 선언된 $private가 하위클래스로 전혀 상속되지 않으므로 객체 $obj2를 통해 접근한 $private는 처음 만나는 새로운 변수로 처리하고 있다는 것을 알 수 있습니다.
16번행 "$obj2->foo2()"를 실행할 때는 역시 문제없이 잘 실행됩니다. 하위클래스 my_class2에 선언된 메쏘드 foo2() 내에서는 같은 클래스에 선언된 private 변수 $private를 접근하는 데 아무런 문제가 없었다는 것을 알 수 있습니다. 따라서 16번행을 실행하면 메쏘드 foo2()를 통해(5번행) 하위클래스 my_class2에서 선언한 private 변수 $private의 값인 "my_class2::private!\n이 정상적으로 출력됩니다.
private 메쏘드
아래의 예제는 private로 선언된 메쏘드가 해당 클래스와 그 하위 클래스에서 어떻게 동작하는가를 알아보기 위해 작성된 것입니다. 각 행을 실행하면서 발생되는 에러메시지에 대하여는 그 행 바로 위에 주석으로 기술하였으니 참조바랍니다.
[code php;gutter:true] class my_class { private $private1 = "my_class::private1!\n; private $private2 = "my_class::private2!\n; private function my_private1() { return "my_class::my_private1()!\n; } private function my_private2() { return "my_class::my_private2()!\n; } function foo() { print "my_class::foo() " . $this->my_private1(); print "my_class::foo() " . $this->my_private2(); } } $obj = new my_class; /** * Fatal error: Call to private method my_class::my_private1() */ print $obj->my_private1(); $obj->foo(); class my_class2 extends my_class { private $private1 = "my_class2::private1!\n; private function my_private1() { return "my_class2::my_private1()!\n; } function foo2() { print "my_class::foo2() " . $this->my_private1(); /** * Fatal error: Call to private method my_class::my_private2() * from context 'my_class2' */ print "my_class::foo2() " . $this->my_private2(); } } $obj2 = new my_class2; /** * Fatal error: Call to private method my_class2::my_private1() */ print $obj2->my_private1(); /** * Fatal error: Call to private method my_class::my_private2() */ print $obj2->my_private2(); $obj2->foo2(); [/code]
< private로 지정된 메쏘드의 동작 >
42번행과 56번행을 실행하면 아래와 같은 에러메시지를 보게 됩니다.
Fatal error: Call to private method my_class::my_private2() ...
에러메시지를 보면 하위클래스 my_class2로 부터 생성된 객체 $obj2를 통해 my_private2()에 접근을 시도하면 이 메쏘드가 my_class2에는 존재하지 않고, 그 상위클래스에 private로 선언된 메쏘드임을 알 수 있습니다.
위의 에러메시지는 아래와 같이 보여주는 것이 PPP 접근제한자의 개념상 더 좋지 않을까 생각됩니다.
Fatal error: Call to undefined method my_class2::my_private2() in xxx.php on line xx
private 변수 항목에서 살펴보았듯이 이는 상위클래스에서 private로 선언되고 그 하위클래스에서는 선언되지 않은 변수를 하위클래스를 통해 접근할 때 아래와 같은 에러메시지를 보여주는 것과 비교하면 이해가 쉬울 것입니다.
Notice: Undefined property: my_class2::$private in xxxxxxxx.php on line 14

Posted by 방글24
phpclass/객체모델2005. 10. 11. 12:07
written: Sep 30 2005
last modified: Oct 11 2005
PPP 접근제한자(Private/Protected/Public access modifier)
C++ 또는 자바와 같은 프로그래밍 언어에는 멤버에 대한 접근 권한(access right)을 지정하는 private, protected, public라는 접근제한자(access modifier;접근지정자, 접근변경자, 접근수정자)가 있습니다. 여러분이 작성하고자 하는 코드를 더 잘 캡슐화(encapsulation)하기 위하여 접근제한자를 통해 멤버에 대한 접근을 제어하는 것입니다.
PHP에서도 버전 5.0부터 PPP 접근제한자를 이용할 수 있게 되어 객체지향 프로그램으로써 한 발 더 다가설 수 있게 되었습니다.
private : 해당 클래스 내에서만 접근할 수 있으며 하위클래스 또는 다른 클래스에서는 접근할 수 없다.
protected : 해당 클래스 및 클래스를 상속받은 하위클래스에서만 접근할 수 있다.
public : 모든 프로그램 영역에서 접근할 수 있다.
아래의 표는 각각의 접근제한자들이 각 프로그램 영역에서의 가시범위에 어떻게 포함되는지를 보여줍니다.
< 각 프로그램영역의 가시범위 >

\

가시범위

----------------

 프로그램영역

\

private

protected

public

동일클래스

O

O

O

하위클래스

X

O

O

다른클래스

X

X

O

전역영역

X

X

O

멤버의 PPP 보호 및 상속
(추가 2005.10.11)
하위클래스에서 상위클래스의 멤버를 재정의할 때는 원래 멤버에 지정된 PPP 접근제한자와 동일하거나 더 public하게 만들어야 합니다. 상위클래스에서보다 더 private한 멤버는 만들 수 없습니다.
  • 상위클래스에서 public로 지정된 멤버는 모든 하위클래스에서도 public 멤버로 지정하여야 합니다.
  • 상위클래스에서 protected로 지정된 멤버는 하위클래스에서 protected 또는 public 멤버로 지정하여야 합니다.
  • 상위클래스에서 private로 지정된 멤버는 상속되지 않으므로 하위클래스에서 동일한 이름의 멤버를 지정하더라도 상위클래스의 멤버와는 전혀 다른 새로운 멤버이므로 PPP 지정에 제한이 없습니다.
< 멤버의 PPP 보호 및 상속 >
상위클래스에서 지정된 접근제한자 private protected public

하위클래스에서 지정할 수 있는

접근제한자

상속안됨

protected

public

public

접근제한자와 관련된 코딩지침
  • 변수는 private로 선언하여 외부에서 접근할 수 없도록 하며, private 변수에 대한 작업이 필요하면 이들에 접근할 수 있는 메쏘드를 작성합니다.
  • 메쏘드도 외부에서 접근할 필요가 없다고 판단되면 private로 선언합니다. 즉, public 메쏘드를 최소화할 수 있도록 클래스를 설계합니다.
캡슐화(encapsulation)와 정보은닉(information hiding)
클래스에는 많은 변수와 메쏘드가 존재합니다만 이들 모두가 외부에서 접근할 필요가 있는 것은 아닙니다.  외부에서 접근해야 하는 메쏘드(이를 accessor method라 함)만을 public로 지정해주며, 나머지 모든 멤버는 private로 지정하여 클래스 내부 동작을 숨기게 됩니다. 이와 같이 내부 동작을 숨기는 것을 정보은닉(information hiding)이라 하며 이와같이 내부의 세부동작을 외부로부터 숨기므로 클래스를 사용하는 외부의 동작과 관계없이 클래스 내부 동작을 쉽게 변화시킬 수 있습니다.
클래스를 설계할 때는 외부에서 접근할 수 있는 멤버와 클래스 내부에서만 접근할 수 있는 멤버를 구분하여 클래스 내부에서 수행되는 부분을 감추게 되는데 이러한 작업을 캡슐화(encapsulation)라고 합니다. 캡슐화는 객체의 내부 동작을 숨기며, 객체가 정의한 인터페이스를 통해서만 접근할 수 있도록 하는 것입니다.
PPP 접근제한자를 이용하여 클래스 멤버의 가시범위를 적절히 제한함으로 정보은닉을 통해 클래스를 캡슐화하여 객체간의 상호의존성을 줄일 수 있으며 코드의 유지관리와 재사용을 높일 수 있습니다.

Posted by 방글24