인터페이스를 통한 다형성
Coddy PHP 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 91개 중 34번째.
이전 레슨에서 자식 클래스가 부모 메서드를 오버라이드하는 상속을 통한 다형성을 살펴보았습니다. 인터페이스는 특히 공통된 동작을 공유하는 서로 관련이 없는 클래스들과 작업할 때, 다형성을 구현하기 위한 또 다른 강력한 방법을 제공합니다.
인터페이스를 사용하면 다형성은 계약 자체에서 비롯됩니다. 인터페이스를 구현하는 모든 클래스는 상속 계층 구조에 관계없이 특정 메서드를 가지고 있음을 보장합니다:
<?php
interface Notifiable {
public function send(string $message);
}
class EmailNotifier implements Notifiable {
public function send(string $message) {
return "Email: " . $message;
}
}
class SMSNotifier implements Notifiable {
public function send(string $message) {
return "SMS: " . $message;
}
}
class PushNotifier implements Notifiable {
public function send(string $message) {
return "Push: " . $message;
}
}
function notify(Notifiable $notifier, string $message) {
return $notifier->send($message);
}
echo notify(new EmailNotifier(), "Hello") . "\n";
echo notify(new SMSNotifier(), "Hello") . "\n";
echo notify(new PushNotifier(), "Hello");
출력:
Email: Hello
SMS: Hello
Push: Hellonotify() 함수는 어떤 Notifiable 객체와도 작동합니다. 이 클래스들은 부모 클래스를 공유하지 않으며, 동일한 인터페이스를 구현한다는 점을 제외하면 서로 완전히 무관합니다. 이것이 상속 기반 다형성에 비해 갖는 핵심적인 장점입니다. 즉, 조상이 아닌 기능에 따라 서로 관련 없는 클래스들을 그룹화할 수 있습니다.
핵심 포인트: 인터페이스 기반 다형성을 사용하면 서로 관련 없는 클래스들이 공유된 동작을 바탕으로 상호 교환적으로 사용될 수 있으며, 이를 통해 코드를 더 유연하게 만들고 구체적인 구현으로부터 결합도를 낮출 수 있습니다.
챌린지
쉬움인터페이스 기반 다형성을 보여주는 스토리지 시스템을 구축해 보겠습니다. 서로 공통점은 없지만 동일한 스토리지 인터페이스를 구현하는 두 가지 다른 스토리지 백엔드인 파일 시스템과 데이터베이스를 만들 것입니다. 단일 함수가 모든 스토리지 유형과 함께 작동하며, 인터페이스가 완전히 관련 없는 클래스 간에 어떻게 다형성을 가능하게 하는지 보여줄 것입니다.
코드는 네 개의 파일로 구성됩니다:
Storable.php—save($key, $data)와retrieve($key)라는 두 가지 메서드 시그니처를 가진Storable인터페이스를 정의합니다. 이 계약은 내부 구현 방식에 상관없이 모든 스토리지 시스템이 데이터를 저장하고 검색할 수 있도록 보장합니다.FileStorage.php—Storable을 구현하는FileStorage클래스를 생성합니다. 인터페이스 파일을 포함하세요. 클래스는 생성자를 통해 설정되는 private$directory속성을 가져야 합니다.save($key, $data)를 구현하여"Saving '$data' to file $this->directory/$key.txt"를 반환하도록 합니다.retrieve($key)를 구현하여"Reading from file $this->directory/$key.txt"를 반환하도록 합니다.DatabaseStorage.php— 역시Storable을 구현하는DatabaseStorage클래스를 생성합니다. 인터페이스 파일을 포함하세요. 클래스는 생성자를 통해 설정되는 private$tableName속성을 가져야 합니다.save($key, $data)를 구현하여"Inserting '$data' into table $this->tableName with key $key"를 반환하도록 합니다.retrieve($key)를 구현하여"Selecting from table $this->tableName where key = $key"를 반환하도록 합니다.main.php— 두 스토리지 파일을 모두 포함합니다.Storable파라미터, 키, 데이터를 인자로 받는storeData함수를 만듭니다. 이 함수는 키와 데이터를 사용하여save()를 호출한 결과를 반환해야 합니다.Storable파라미터와 키를 인자로 받는fetchData라는 또 다른 함수를 만들고,retrieve()를 호출한 결과를 반환합니다.
디렉토리 경로, 테이블 이름, 키, 저장할 데이터 등 네 가지 입력을 받게 됩니다. 디렉토리로 FileStorage를 생성하고 테이블 이름으로 DatabaseStorage를 생성합니다. storeData() 함수를 사용하여 먼저 파일 스토리지를 사용해 데이터를 저장한 다음, 데이터베이스 스토리지를 사용해 저장합니다. 각 결과를 별도의 줄에 출력합니다. 그런 다음 fetchData()를 사용하여 파일 스토리지에서 데이터를 검색하고 그 결과를 출력합니다.
storeData()와 fetchData()가 두 스토리지 유형에서 어떻게 동일하게 작동하는지 주목하세요. 이 함수들은 객체가 Storable을 구현하고 있다는 사실만 신경 씁니다. 파일 시스템과 데이터베이스는 완전히 다른 내부 구현을 가지고 있지만, 인터페이스 계약을 통해 서로 교체 가능합니다.
치트 시트
인터페이스는 상속 계층 구조가 아닌 공유된 동작을 기반으로 서로 관련 없는 클래스들을 상호 교환하여 사용할 수 있게 함으로써 다형성을 가능하게 합니다.
인터페이스를 구현하는 모든 클래스는 특정 메서드를 가질 것을 보장하며, 이는 다형성 동작을 가능하게 하는 계약을 생성합니다:
<?php
interface Notifiable {
public function send(string $message);
}
class EmailNotifier implements Notifiable {
public function send(string $message) {
return "Email: " . $message;
}
}
class SMSNotifier implements Notifiable {
public function send(string $message) {
return "SMS: " . $message;
}
}
function notify(Notifiable $notifier, string $message) {
return $notifier->send($message);
}
echo notify(new EmailNotifier(), "Hello");
echo notify(new SMSNotifier(), "Hello");notify() 함수는 클래스 계층 구조에 관계없이 Notifiable을 구현하는 모든 객체를 허용합니다. 이는 코드를 더 유연하게 만들고 특정 구현으로부터 분리(decoupled)시킵니다.
주요 장점: 인터페이스 기반 다형성은 관련 없는 클래스들을 조상이 아닌 그들의 기능별로 그룹화하여, 공통된 계약을 통해 완전히 다른 구현체들을 상호 교환하여 사용할 수 있게 합니다.
직접 해보기
<?php
require_once 'FileStorage.php';
require_once 'DatabaseStorage.php';
// 입력 읽기
$directory = trim(fgets(STDIN));
$tableName = trim(fgets(STDIN));
$key = trim(fgets(STDIN));
$data = trim(fgets(STDIN));
// TODO: Storable, key, data를 인자로 받는 storeData 함수를 생성하세요
// key와 data를 사용하여 save()를 호출한 결과를 반환해야 합니다
// TODO: Storable과 key를 인자로 받는 fetchData 함수를 생성하세요
// key를 사용하여 retrieve()를 호출한 결과를 반환해야 합니다
// TODO: 디렉토리를 사용하여 FileStorage 인스턴스를 생성하세요
// TODO: 테이블 이름을 사용하여 DatabaseStorage 인스턴스를 생성하세요
// TODO: storeData()를 사용하여 파일 저장소에 데이터를 저장한 후, 결과를 출력하세요
// TODO: storeData()를 사용하여 데이터베이스 저장소에 데이터를 저장한 후, 결과를 출력하세요
// TODO: fetchData()를 사용하여 파일 저장소에서 데이터를 조회한 후, 결과를 출력하세요
?>이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8매직 메서드
매직 메서드 소개__toString 및 __debugInfo__get, __set, __isset, __unset__call 및 __callStatic__clone 및 객체 복제__serialize 및 __unserialize요약 - 커스텀 컬렉션