๐ณ Composite ํจํด: ๋ณต์กํ ๊ตฌ์กฐ๋ฅผ ๋จ์ํ๊ฒ ๋ค๋ฃจ๋ ๋์์ธ ํจํด
๐ ๊ฐ์
Composite ํจํด์ ๊ฐ์ฒด๋ค์ ํธ๋ฆฌ ๊ตฌ์กฐ๋ก ๊ตฌ์ฑํ์ฌ ์ ์ฒด-๋ถ๋ถ ๊ณ์ธต์ ํํํ๋ ๊ตฌ์กฐ์ ๋์์ธ ํจํด์ ๋๋ค. ์ด ํจํด์ ์ฌ์ฉํ๋ฉด ํด๋ผ์ด์ธํธ๊ฐ ๊ฐ๋ณ ๊ฐ์ฒด์ ๊ฐ์ฒด๋ค์ ์กฐํฉ์ ๋์ผํ๊ฒ ๋ค๋ฃฐ ์ ์์ต๋๋ค.
๐ ํต์ฌ ๊ฐ๋
Composite ํจํด์ ํต์ฌ์ ๋จ์ผ ๊ฐ์ฒด์ ๋ณตํฉ ๊ฐ์ฒด๋ฅผ ๋์ผํ ์ธํฐํ์ด์ค๋ก ์ฒ๋ฆฌํ ์ ์๋ค๋ ์ ์ ๋๋ค. ์ด๋ฅผ ํตํด ๋ณต์กํ ํธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๊ฐ๋จํ๊ณ ์ผ๊ด๋ ๋ฐฉ์์ผ๋ก ์กฐ์ํ ์ ์์ต๋๋ค.
๐งฉ ๊ตฌ์ฑ ์์
- ๐ Component: ํธ๋ฆฌ์ ๊ฐ์ฒด๋ค์ ๋ํ ์ธํฐํ์ด์ค๋ฅผ ์ ์
- ๐ Leaf: ํธ๋ฆฌ์ ์ ๋ ธ๋๋ฅผ ๋ํ๋ด๋ฉฐ, ์์์ด ์๋ ๊ฐ์ฒด
- ๐ฆ Composite: ์์๋ค์ ๊ฐ์ง๋ ์ปดํฌ๋ํธ์ ํ๋์ ์ ์ํ๊ณ ์์ ์ปดํฌ๋ํธ๋ค์ ์ ์ฅ
โ๏ธ ์ฅ์ ๊ณผ ๋จ์
โ ์ฅ์
- ์ผ๊ด๋ ์ธํฐํ์ด์ค: ๋จ์ผ ๊ฐ์ฒด์ ๋ณตํฉ ๊ฐ์ฒด๋ฅผ ๋์ผํ๊ฒ ์ฒ๋ฆฌ
- ํ์ฅ์ฑ: ์๋ก์ด ์ข ๋ฅ์ ์ปดํฌ๋ํธ๋ฅผ ์ฝ๊ฒ ์ถ๊ฐ ๊ฐ๋ฅ
- ํฌ๋ช ์ฑ: ํด๋ผ์ด์ธํธ ์ฝ๋๊ฐ ๋จ์ํด์ง
โ ๏ธ ๋จ์
- ํ์ ์์ ์ฑ: ์ปดํฌ๋ํธ์ ํ์ ์ ์ ํํ๊ธฐ ์ด๋ ค์
- ๋ณต์ก์ฑ: ๋์์ธ์ด ์ง๋์น๊ฒ ์ผ๋ฐ์ ์ด ๋ ์ ์์
๐ข ์ค์ ์์ 1: ํ์ฌ ์กฐ์ง๋ (Java)
ํ์ฌ์ ์กฐ์ง ๊ตฌ์กฐ๋ฅผ Composite ํจํด์ผ๋ก ๊ตฌํํด๋ณด๊ฒ ์ต๋๋ค.
// Component ์ธํฐํ์ด์ค
interface Employee {
void showDetails();
double getSalary();
void add(Employee employee);
void remove(Employee employee);
}
// Leaf ํด๋์ค - ์ผ๋ฐ ์ง์
class Developer implements Employee {
private String name;
private double salary;
private String technology;
public Developer(String name, double salary, String technology) {
this.name = name;
this.salary = salary;
this.technology = technology;
}
@Override
public void showDetails() {
System.out.println("Developer: " + name + ", Salary: " + salary +
", Tech: " + technology);
}
@Override
public double getSalary() {
return salary;
}
@Override
public void add(Employee employee) {
throw new UnsupportedOperationException("๊ฐ๋ฐ์๋ ์ง์์ ์ถ๊ฐํ ์ ์์ต๋๋ค.");
}
@Override
public void remove(Employee employee) {
throw new UnsupportedOperationException("๊ฐ๋ฐ์๋ ์ง์์ ์ ๊ฑฐํ ์ ์์ต๋๋ค.");
}
}
// Composite ํด๋์ค - ๋งค๋์
class Manager implements Employee {
private String name;
private double salary;
private List<Employee> subordinates;
public Manager(String name, double salary) {
this.name = name;
this.salary = salary;
this.subordinates = new ArrayList<>();
}
@Override
public void showDetails() {
System.out.println("Manager: " + name + ", Salary: " + salary);
System.out.println("ํ์๋ค:");
for (Employee emp : subordinates) {
System.out.print(" ");
emp.showDetails();
}
}
@Override
public double getSalary() {
double totalSalary = salary;
for (Employee emp : subordinates) {
totalSalary += emp.getSalary();
}
return totalSalary;
}
@Override
public void add(Employee employee) {
subordinates.add(employee);
}
@Override
public void remove(Employee employee) {
subordinates.remove(employee);
}
}
// ์ฌ์ฉ ์์
public class CompanyExample {
public static void main(String[] args) {
// ๊ฐ๋ฐ์๋ค ์์ฑ
Employee dev1 = new Developer("๊น๊ฐ๋ฐ", 5000, "Java");
Employee dev2 = new Developer("์ดํ๋ก ํธ", 4800, "React");
Employee dev3 = new Developer("๋ฐ๋ฐฑ์๋", 5200, "Spring");
// ํ์ฅ ์์ฑ
Employee teamLead = new Manager("์ตํ์ฅ", 7000);
teamLead.add(dev1);
teamLead.add(dev2);
// ๋ถ์์ฅ ์์ฑ
Employee deptHead = new Manager("์ ๋ถ์ฅ", 10000);
deptHead.add(teamLead);
deptHead.add(dev3);
// ์กฐ์ง๋ ์ถ๋ ฅ
System.out.println("=== ์กฐ์ง๋ ===");
deptHead.showDetails();
System.out.println("\n=== ์ด ๊ธ์ฌ ===");
System.out.println("๋ถ์ ์ ์ฒด ๊ธ์ฌ: " + deptHead.getSalary());
}
}
๐ฅ๏ธ ์ค์ ์์ 2: UI ์ปดํฌ๋ํธ ์์คํ (TypeScript)
์น UI์ ์ปดํฌ๋ํธ ๊ตฌ์กฐ๋ฅผ Composite ํจํด์ผ๋ก ๊ตฌํํด๋ณด๊ฒ ์ต๋๋ค.
// Component ์ธํฐํ์ด์ค
interface UIComponent {
render(): string;
getSize(): number;
add?(component: UIComponent): void;
remove?(component: UIComponent): void;
}
// Leaf ํด๋์ค - ๊ธฐ๋ณธ UI ์์๋ค
class Button implements UIComponent {
constructor(private text: string, private onClick: string) {}
render(): string {
return `<button onclick="${this.onClick}">${this.text}</button>`;
}
getSize(): number {
return 1; // ๊ธฐ๋ณธ ์ปดํฌ๋ํธ ํฌ๊ธฐ
}
}
class TextInput implements UIComponent {
constructor(private placeholder: string, private name: string) {}
render(): string {
return `<input type="text" name="${this.name}" placeholder="${this.placeholder}" />`;
}
getSize(): number {
return 1;
}
}
class Label implements UIComponent {
constructor(private text: string) {}
render(): string {
return `<label>${this.text}</label>`;
}
getSize(): number {
return 1;
}
}
// Composite ํด๋์ค - ์ปจํ
์ด๋๋ค
class Panel implements UIComponent {
private components: UIComponent[] = [];
constructor(private cssClass: string = '') {}
add(component: UIComponent): void {
this.components.push(component);
}
remove(component: UIComponent): void {
const index = this.components.indexOf(component);
if (index > -1) {
this.components.splice(index, 1);
}
}
render(): string {
const content = this.components
.map(component => component.render())
.join('\n ');
const classAttr = this.cssClass ? ` class="${this.cssClass}"` : '';
return `<div${classAttr}>\n ${content}\n</div>`;
}
getSize(): number {
return this.components.reduce((total, component) =>
total + component.getSize(), 0);
}
}
class Form implements UIComponent {
private components: UIComponent[] = [];
constructor(private action: string, private method: string = 'POST') {}
add(component: UIComponent): void {
this.components.push(component);
}
remove(component: UIComponent): void {
const index = this.components.indexOf(component);
if (index > -1) {
this.components.splice(index, 1);
}
}
render(): string {
const content = this.components
.map(component => component.render())
.join('\n ');
return `<form action="${this.action}" method="${this.method}">\n ${content}\n</form>`;
}
getSize(): number {
return this.components.reduce((total, component) =>
total + component.getSize(), 0);
}
}
// ์ฌ์ฉ ์์
class UIExample {
static createLoginForm(): UIComponent {
// ๊ธฐ๋ณธ ์ปดํฌ๋ํธ๋ค ์์ฑ
const usernameLabel = new Label('์ฌ์ฉ์๋ช
:');
const usernameInput = new TextInput('์ฌ์ฉ์๋ช
์ ์
๋ ฅํ์ธ์', 'username');
const passwordLabel = new Label('๋น๋ฐ๋ฒํธ:');
const passwordInput = new TextInput('๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํ์ธ์', 'password');
const loginButton = new Button('๋ก๊ทธ์ธ', 'handleLogin()');
const cancelButton = new Button('์ทจ์', 'handleCancel()');
// ์ฌ์ฉ์๋ช
ํ๋ ํจ๋
const usernamePanel = new Panel('form-field');
usernamePanel.add(usernameLabel);
usernamePanel.add(usernameInput);
// ๋น๋ฐ๋ฒํธ ํ๋ ํจ๋
const passwordPanel = new Panel('form-field');
passwordPanel.add(passwordLabel);
passwordPanel.add(passwordInput);
// ๋ฒํผ ํจ๋
const buttonPanel = new Panel('button-group');
buttonPanel.add(loginButton);
buttonPanel.add(cancelButton);
// ์ ์ฒด ํผ
const loginForm = new Form('/login');
loginForm.add(usernamePanel);
loginForm.add(passwordPanel);
loginForm.add(buttonPanel);
return loginForm;
}
static main(): void {
const loginForm = UIExample.createLoginForm();
console.log('=== ๋ก๊ทธ์ธ ํผ HTML ===');
console.log(loginForm.render());
console.log('\n=== ์ปดํฌ๋ํธ ๊ฐ์ ===');
console.log(`์ด ์ปดํฌ๋ํธ ์: ${loginForm.getSize()}`);
}
}
// ์คํ
UIExample.main();
๐ค ์ธ์ ์ฌ์ฉํด์ผ ํ ๊น?
Composite ํจํด์ ๋ค์๊ณผ ๊ฐ์ ์ํฉ์์ ์ ์ฉํฉ๋๋ค:
- ๐ฒ ํธ๋ฆฌ ๊ตฌ์กฐ ํํ: ์ ์ฒด-๋ถ๋ถ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ํํํด์ผ ํ ๋
- ๐ ์ผ๊ด๋ ์ฒ๋ฆฌ: ๊ฐ๋ณ ๊ฐ์ฒด์ ๊ฐ์ฒด ๊ทธ๋ฃน์ ๋์ผํ๊ฒ ์ฒ๋ฆฌํ๊ณ ์ถ์ ๋
- ๐ ์ฌ๊ท์ ๊ตฌ์กฐ: ์ค์ฒฉ๋ ๊ตฌ์กฐ๋ฅผ ๊ฐ๋จํ๊ฒ ์ฒ๋ฆฌํ๊ณ ์ถ์ ๋
๐ ์ค์ ํ์ฉ ์ฌ๋ก
- โ๏ธ UI ํ๋ ์์ํฌ: React, Vue ๋ฑ์ ์ปดํฌ๋ํธ ์์คํ
- ๐จ ๊ทธ๋ํฝ ์์คํ : ๋ํ๋ค์ ๊ทธ๋ฃนํ ์ฒ๋ฆฌ
- ๐ ํ์ผ ์์คํ : ํด๋์ ํ์ผ์ ๊ณ์ธต ๊ตฌ์กฐ
- ๐ฅ ์กฐ์ง๋: ํ์ฌ๋ ํ์ ๊ณ์ธต ๊ตฌ์กฐ
- ๐ ๋ฉ๋ด ์์คํ : ์ค์ฒฉ๋ ๋ฉ๋ด ๊ตฌ์กฐ
๐ฏ ๊ฒฐ๋ก
Composite ํจํด์ ๋ณต์กํ ํธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋จ์ํ๊ณ ์ผ๊ด๋ ๋ฐฉ์์ผ๋ก ๋ค๋ฃฐ ์ ์๊ฒ ํด์ฃผ๋ ๊ฐ๋ ฅํ ํจํด์ ๋๋ค. ํนํ ๊ณ์ธต์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง ์์คํ ์์ ์ฝ๋์ ๋ณต์ก์ฑ์ ํฌ๊ฒ ์ค์ด๊ณ ์ ์ง๋ณด์์ฑ์ ํฅ์์ํฌ ์ ์์ต๋๋ค.
ํจํด์ ์ ์ฉํ ๋๋ ๋จ์ผ ๊ฐ์ฒด์ ๋ณตํฉ ๊ฐ์ฒด ๊ฐ์ ์ผ๊ด๋ ์ธํฐํ์ด์ค ์ค๊ณ๊ฐ ํต์ฌ์ด๋ฉฐ, ์ด๋ฅผ ํตํด ํด๋ผ์ด์ธํธ ์ฝ๋๋ฅผ ๋จ์ํํ๊ณ ํ์ฅ์ฑ์ ํ๋ณดํ ์ ์์ต๋๋ค.
๋๊ธ