컴포지트 패턴
Coddy PHP 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 91개 중 80번째.
컴포지트 패턴(Composite Pattern)은 객체들을 트리 구조로 구성한 뒤, 이러한 구조를 개별 객체인 것처럼 다룰 수 있게 해주는 구조적 디자인 패턴입니다. 이는 단일 객체와 객체 그룹을 동일하게 취급하려는 부분-전체 계층 구조를 표현하는 데 완벽한 방법입니다.
파일 시스템을 생각해 봅시다. 폴더는 파일과 다른 폴더를 포함할 수 있습니다.
전체 크기를 계산할 때, 파일과 폴더 모두에서 동일한 메서드를 호출하고 싶을 것이며, 폴더는 그 내용을 재귀적으로 합산해야 합니다. 컴포지트 패턴(Composite Pattern)은 리프(leaf, 파일)와 컴포지트(composite, 폴더) 모두에게 동일한 인터페이스를 제공함으로써 이를 매끄럽게 처리할 수 있게 해줍니다.
<?php
interface FileSystemItem {
public function getName(): string;
public function getSize(): int;
}
class File implements FileSystemItem {
public function __construct(
private string $name,
private int $size
) {}
public function getName(): string {
return $this->name;
}
public function getSize(): int {
return $this->size;
}
}
class Folder implements FileSystemItem {
private array $items = [];
public function __construct(private string $name) {}
public function getName(): string {
return $this->name;
}
public function add(FileSystemItem $item): void {
$this->items[] = $item;
}
public function getSize(): int {
$total = 0;
foreach ($this->items as $item) {
$total += $item->getSize();
}
return $total;
}
}
이제 트리 구조를 구축하고 이를 일관되게 처리할 수 있습니다:
<?php
$docs = new Folder("Documents");
$docs->add(new File("resume.pdf", 250));
$docs->add(new File("cover.docx", 50));
$images = new Folder("Images");
$images->add(new File("photo.jpg", 1500));
$root = new Folder("Root");
$root->add($docs);
$root->add($images);
$root->add(new File("readme.txt", 10));
echo $root->getSize();
출력:
1810클라이언트 코드는 단일 파일인지 아니면 깊게 중첩된 폴더 구조인지 신경 쓰지 않고 getSize()를 호출합니다. 이 패턴은 내부적으로 재귀를 처리하여 복잡한 계층 구조를 쉽게 다룰 수 있게 해줍니다.
챌린지
쉬움컴포지트 패턴(Composite Pattern)을 사용하여 조직도 시스템을 구축해 보겠습니다. 회사는 부서가 직원과 다른 하위 부서를 포함할 수 있는 계층 구조를 가지고 있습니다. 이는 조직 전체에 걸쳐 합계를 균일하게 계산하려는 경우에 적합한 트리 구조입니다.
코드는 다음 네 개의 파일로 구성됩니다:
OrganizationUnit.php— 개별 직원과 부서가 모두 구현할OrganizationUnit인터페이스를 정의합니다. 이 인터페이스는getName(): string과getSalary(): int라는 두 가지 메서드를 가져야 합니다. 이 공통 인터페이스를 통해 비용을 계산할 때 단일 직원과 전체 부서를 동일한 방식으로 처리할 수 있습니다.Employee.php— OrganizationUnit 인터페이스를 포함하고 이를 구현하는Employee클래스를 생성합니다. 직원은 조직 트리의 리프 노드(leaf node)입니다. 이름과 급여를 가지지만 다른 단위를 포함할 수는 없습니다. 생성자 프로모션(constructor promotion)을 사용하여 이름과 급여를 받고, 두 인터페이스 메서드를 모두 구현하세요.Department.php— OrganizationUnit 인터페이스를 포함하고 이를 구현하는Department클래스를 생성합니다. 부서는 직원과 다른 부서를 모두 포함할 수 있는 컴포지트(composite)입니다. 부서 클래스는 다음을 수행해야 합니다:- 생성자에서 이름을 받습니다.
- 직원이나 하위 부서를 추가하기 위한
add(OrganizationUnit $unit): void메서드를 가집니다. - 포함된 모든 단위의 총 급여를 반환하도록
getSalary()를 구현합니다 (내부의 모든 항목을 재귀적으로 합산).
main.php— Employee 및 Department 파일을 포함합니다. 조직 구조를 나타내는 JSON 문자열이라는 하나의 입력을 받게 됩니다.JSON은 다음과 같은 형식입니다:
{"Engineering": [{"name": "Alice", "salary": 80000}, {"name": "Bob", "salary": 75000}], "Sales": [{"name": "Carol", "salary": 65000}]}각 키는 부서 이름이며, 값은 이름과 급여를 가진 직원들의 배열입니다.
"Company"라는 이름의 루트Department를 생성합니다. JSON의 각 부서에 대해Department를 생성하고 직원을 추가한 다음, 해당 부서를 Company에 추가합니다. 마지막으로 다음과 같은 형식으로 회사의 총 급여를 출력합니다:Total Salary: [amount]
컴포지트 패턴의 장점은 Company 부서에서 getSalary()를 호출하면 모든 중첩된 부서와 직원의 합계가 자동으로 계산된다는 것입니다. 클라이언트 코드는 내부 구조를 알 필요가 없습니다.
치트 시트
컴포지트 패턴(Composite Pattern)은 객체들을 트리 구조로 구성하여 개별 객체와 복합 객체를 균일하게 다룰 수 있게 하는 구조적 디자인 패턴입니다.
이 패턴은 리프 노드(leaf node)와 복합 노드(composite node) 모두에 대해 동일한 인터페이스를 사용하고자 하는 부분-전체 계층 구조에 이상적입니다.
주요 구성 요소
- Component Interface — 리프와 복합체 모두를 위한 공통 작업을 정의합니다
- Leaf — 다른 객체를 포함할 수 없는 개별 객체를 나타냅니다
- Composite — 자식(리프 또는 다른 복합체)을 포함하며, 자식에게 작업을 위임하여 기능을 구현합니다
예제: 파일 시스템
<?php
interface FileSystemItem {
public function getName(): string;
public function getSize(): int;
}
class File implements FileSystemItem {
public function __construct(
private string $name,
private int $size
) {}
public function getName(): string {
return $this->name;
}
public function getSize(): int {
return $this->size;
}
}
class Folder implements FileSystemItem {
private array $items = [];
public function __construct(private string $name) {}
public function getName(): string {
return $this->name;
}
public function add(FileSystemItem $item): void {
$this->items[] = $item;
}
public function getSize(): int {
$total = 0;
foreach ($this->items as $item) {
$total += $item->getSize();
}
return $total;
}
}
사용법
<?php
$docs = new Folder("Documents");
$docs->add(new File("resume.pdf", 250));
$docs->add(new File("cover.docx", 50));
$images = new Folder("Images");
$images->add(new File("photo.jpg", 1500));
$root = new Folder("Root");
$root->add($docs);
$root->add($images);
$root->add(new File("readme.txt", 10));
echo $root->getSize(); // 1810
클라이언트는 파일과 폴더 모두에서 getSize()를 균일하게 호출합니다. 복합체는 내부적으로 재귀를 처리하여 복잡한 계층 구조를 간단하게 다룰 수 있게 해줍니다.
직접 해보기
<?php
require_once 'Employee.php';
require_once 'Department.php';
// JSON 입력을 읽습니다.
$input = trim(fgets(STDIN));
$data = (array)json_decode($input, true);
// TODO: "Company"라는 이름의 루트 부서(Department)를 생성합니다.
// TODO: JSON 데이터의 각 부서를 루프(순회)합니다.
// 각 부서에 대해:
// - 새로운 부서(Department) 객체를 생성합니다.
// - 각 직원을 해당 부서에 추가합니다.
// - 해당 부서를 Company에 추가합니다.
// TODO: 총 급여를 다음 형식으로 출력합니다: Total Salary: [amount]
?>이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8매직 메서드
매직 메서드 소개__toString 및 __debugInfo__get, __set, __isset, __unset__call 및 __callStatic__clone 및 객체 복제__serialize 및 __unserialize요약 - 커스텀 컬렉션