컴포지트 패턴
Coddy C# 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 70개 중 61번째.
컴포지트 패턴(Composite pattern)은 객체들을 트리 구조로 구성하여 개별 객체와 객체 그룹을 균일하게 다룰 수 있도록 하는 구조 패턴입니다. 이는 단일 항목과 컬렉션 모두를 동일한 인터페이스를 통해 작업하고자 하는 부분-전체 계층 구조를 표현하는 데 완벽합니다.
이 패턴은 세 가지 핵심 구성 요소를 가집니다: 공통 작업을 정의하는 컴포넌트 인터페이스, 개별 객체를 나타내는 리프 클래스, 그리고 자식을 포함하고 동일한 인터페이스를 구현하는 복합 클래스입니다:
public interface IFileSystemItem
{
string Name { get; }
int GetSize();
}
public class File : IFileSystemItem
{
public string Name { get; }
private int _size;
public File(string name, int size)
{
Name = name;
_size = size;
}
public int GetSize() => _size;
}
public class Folder : IFileSystemItem
{
public string Name { get; }
private List<IFileSystemItem> _items = new List<IFileSystemItem>();
public Folder(string name) => Name = name;
public void Add(IFileSystemItem item) => _items.Add(item);
public int GetSize() => _items.Sum(item => item.GetSize());
}이 패턴의 장점은 클라이언트가 단일 파일로 작업하는지 아니면 전체 폴더 계층 구조로 작업하는지 알 필요가 없다는 점입니다:
var root = new Folder("root");
root.Add(new File("doc.txt", 100));
var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));
root.Add(subFolder);
Console.WriteLine(root.GetSize()); // 650컴포지트 패턴은 파일 시스템, UI 컴포넌트 트리, 조직도 또는 컨테이너와 내용물을 동일한 방식으로 처리해야 하는 모든 구조에 이상적입니다. 이는 단순한 요소와 복잡한 요소를 구분할 필요를 없애 클라이언트 코드를 단순화합니다.
챌린지
쉬움컴포지트(Composite) 패턴을 사용하여 조직도 시스템을 구축해 보겠습니다. 개별 직원과 부서(직원 또는 다른 부서를 포함하는)를 동일하게 취급할 수 있는 구조를 만들어, 계층 구조의 어느 수준에서든 총 급여 비용을 계산할 수 있도록 할 것입니다.
코드는 다음 세 개의 파일로 구성됩니다:
OrgComponent.cs:Organization네임스페이스 안에IOrgComponent인터페이스를 정의합니다. 이 인터페이스는Name속성(string)과 정수를 반환하는GetSalary()메서드라는 두 개의 멤버를 가집니다. 이 인터페이스는 개별 직원과 부서가 모두 따라야 하는 공통 규약을 나타냅니다.OrgClasses.cs: 동일한 네임스페이스에 두 개의 클래스를 생성합니다:Employee- 개별 직원을 나타내는 리프(leaf) 클래스입니다. 생성자에서 이름과 급여를 받으며, 자신의 급여를 반환함으로써 인터페이스를 구현합니다.Department- 그룹을 나타내는 컴포지트(composite) 클래스입니다. 생성자에서 이름을 받으며IOrgComponent항목들의 리스트를 유지합니다. 멤버를 추가하기 위한Add(IOrgComponent component)메서드를 포함하세요. 이 클래스의GetSalary()는 모든 멤버 급여의 합계를 반환해야 합니다.
Program.cs: 입력을 기반으로 조직 구조를 구축하고 총 급여를 계산합니다. 부서와 직원을 생성하고 적절하게 중첩시킨 다음, 컴포지트 패턴을 통해 전체 계층 구조를 어떻게 일관되게 다룰 수 있는지 보여줍니다.
다음과 같은 입력을 받게 됩니다:
- 생성할 컴포넌트의 수
- 각 컴포넌트별: 타입(
employee또는department), 이름, 그리고 직원의 경우 급여. 부서의 경우 상위 부서 이름(최상위 부서인 경우root)도 함께 받습니다.
각 컴포넌트의 입력 형식:
employee
{name}
{salary}
{parent_department}
department
{name}
{parent_department}구조를 구축한 후, 루트 부서의 총 급여를 {root_name} Total Salary: {amount} 형식으로 출력하세요.
예를 들어, 입력이 다음과 같다면:
5
department
Engineering
root
employee
Alice
5000
Engineering
employee
Bob
4500
Engineering
department
QA
Engineering
employee
Charlie
4000
QA출력은 다음과 같아야 합니다:
Engineering Total Salary: 13500Engineering 부서는 Alice (5000), Bob (4500), 그리고 QA 부서를 포함합니다. QA 부서는 Charlie (4000)를 포함합니다. Engineering 부서에서 GetSalary()를 호출하면 모든 중첩된 컴포넌트로부터 합계를 재귀적으로 계산합니다. 이는 컴포지트 패턴이 단순하고 일관된 인터페이스를 통해 복잡한 트리 구조를 어떻게 다룰 수 있게 해주는지 잘 보여줍니다!
치트 시트
컴포지트 패턴(Composite pattern)은 객체들을 트리 구조로 구성하고 개별 객체와 그룹을 동일한 인터페이스를 통해 일관되게 다루는 구조적 패턴입니다. 이는 부분-전체 계층 구조에 이상적입니다.
이 패턴은 세 가지 핵심 구성 요소로 이루어집니다:
- 컴포넌트 인터페이스(Component interface): 공통 작업을 정의합니다
- 리프 클래스(Leaf classes): 개별 객체를 나타냅니다
- 컴포지트 클래스(Composite classes): 자식 요소를 포함하며 동일한 인터페이스를 구현합니다
기본 구현 예시:
// 컴포넌트 인터페이스
public interface IFileSystemItem
{
string Name { get; }
int GetSize();
}
// 리프 클래스
public class File : IFileSystemItem
{
public string Name { get; }
private int _size;
public File(string name, int size)
{
Name = name;
_size = size;
}
public int GetSize() => _size;
}
// 컴포지트 클래스
public class Folder : IFileSystemItem
{
public string Name { get; }
private List<IFileSystemItem> _items = new List<IFileSystemItem>();
public Folder(string name) => Name = name;
public void Add(IFileSystemItem item) => _items.Add(item);
public int GetSize() => _items.Sum(item => item.GetSize());
}사용법 - 클라이언트는 단일 항목과 컬렉션을 동일하게 다룹니다:
var root = new Folder("root");
root.Add(new File("doc.txt", 100));
var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));
root.Add(subFolder);
Console.WriteLine(root.GetSize()); // 650컴포지트 패턴은 파일 시스템, UI 컴포넌트 트리, 조직도 또는 컨테이너와 내용물을 동일한 방식으로 처리해야 하는 모든 구조에서 유용합니다.
직접 해보기
using System;
using System.Collections.Generic;
using Organization;
class Program
{
public static void Main(string[] args)
{
int n = Convert.ToInt32(Console.ReadLine());
// TODO: 부서 이름을 키로 하여 부서를 저장할 딕셔너리를 생성하세요
// 이렇게 하면 구성 요소를 추가할 때 상위 부서를 쉽게 찾을 수 있습니다
Dictionary<string, Department> departments = new Dictionary<string, Department>();
// TODO: root 부서를 추적하세요
Department root = null;
for (int i = 0; i < n; i++)
{
string type = Console.ReadLine();
if (type == "department")
{
string name = Console.ReadLine();
string parent = Console.ReadLine();
// TODO: 부서를 생성하세요
// TODO: 생성한 부서를 departments 딕셔너리에 추가하세요
// TODO: parent가 "root"이면, 이것이 root 부서입니다
// TODO: 그렇지 않으면, 상위 부서를 찾아 이 부서를 해당 부서에 추가하세요
}
else if (type == "employee")
{
string name = Console.ReadLine();
int salary = Convert.ToInt32(Console.ReadLine());
string parent = Console.ReadLine();
// TODO: 직원을 생성하세요
// TODO: 상위 부서를 찾아 직원을 해당 부서에 추가하세요
}
}
// TODO: root 부서의 총 급여를 출력하세요
// 형식: "{root_name} Total Salary: {amount}"
Console.WriteLine($"{root.Name} Total Salary: {root.GetSalary()}");
}
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.