๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
develop_kr/์ด๋ก 

๐ŸŽฏ์ปดํฌ์ง€ํŠธ(Composite) ํŒจํ„ด ์™„๋ฒฝ ๊ฐ€์ด๋“œ: Java์™€ TypeScript ์˜ˆ์ œ๋กœ ๋งˆ์Šคํ„ฐํ•˜๊ธฐ

by JSsunday 2025. 7. 2.
728x90
๋ฐ˜์‘ํ˜•

๐ŸŒณ Composite ํŒจํ„ด: ๋ณต์žกํ•œ ๊ตฌ์กฐ๋ฅผ ๋‹จ์ˆœํ•˜๊ฒŒ ๋‹ค๋ฃจ๋Š” ๋””์ž์ธ ํŒจํ„ด

๐Ÿ“‹ ๊ฐœ์š”

Composite ํŒจํ„ด์€ ๊ฐ์ฒด๋“ค์„ ํŠธ๋ฆฌ ๊ตฌ์กฐ๋กœ ๊ตฌ์„ฑํ•˜์—ฌ ์ „์ฒด-๋ถ€๋ถ„ ๊ณ„์ธต์„ ํ‘œํ˜„ํ•˜๋Š” ๊ตฌ์กฐ์  ๋””์ž์ธ ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ์ด ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ฐœ๋ณ„ ๊ฐ์ฒด์™€ ๊ฐ์ฒด๋“ค์˜ ์กฐํ•ฉ์„ ๋™์ผํ•˜๊ฒŒ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”‘ ํ•ต์‹ฌ ๊ฐœ๋…

Composite ํŒจํ„ด์˜ ํ•ต์‹ฌ์€ ๋‹จ์ผ ๊ฐ์ฒด์™€ ๋ณตํ•ฉ ๊ฐ์ฒด๋ฅผ ๋™์ผํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ณต์žกํ•œ ํŠธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๊ฐ„๋‹จํ•˜๊ณ  ์ผ๊ด€๋œ ๋ฐฉ์‹์œผ๋กœ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿงฉ ๊ตฌ์„ฑ ์š”์†Œ

  1. ๐Ÿ“„ Component: ํŠธ๋ฆฌ์˜ ๊ฐ์ฒด๋“ค์— ๋Œ€ํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ •์˜
  2. ๐Ÿƒ Leaf: ํŠธ๋ฆฌ์˜ ์žŽ ๋…ธ๋“œ๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ์ž์‹์ด ์—†๋Š” ๊ฐ์ฒด
  3. ๐Ÿ“ฆ 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 ํŒจํ„ด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค:

  1. ๐ŸŒฒ ํŠธ๋ฆฌ ๊ตฌ์กฐ ํ‘œํ˜„: ์ „์ฒด-๋ถ€๋ถ„ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ํ‘œํ˜„ํ•ด์•ผ ํ•  ๋•Œ
  2. ๐Ÿ”„ ์ผ๊ด€๋œ ์ฒ˜๋ฆฌ: ๊ฐœ๋ณ„ ๊ฐ์ฒด์™€ ๊ฐ์ฒด ๊ทธ๋ฃน์„ ๋™์ผํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ
  3. ๐Ÿ” ์žฌ๊ท€์  ๊ตฌ์กฐ: ์ค‘์ฒฉ๋œ ๊ตฌ์กฐ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ

๐ŸŒ ์‹ค์ œ ํ™œ์šฉ ์‚ฌ๋ก€

  • โš›๏ธ UI ํ”„๋ ˆ์ž„์›Œํฌ: React, Vue ๋“ฑ์˜ ์ปดํฌ๋„ŒํŠธ ์‹œ์Šคํ…œ
  • ๐ŸŽจ ๊ทธ๋ž˜ํ”ฝ ์‹œ์Šคํ…œ: ๋„ํ˜•๋“ค์˜ ๊ทธ๋ฃนํ™” ์ฒ˜๋ฆฌ
  • ๐Ÿ“ ํŒŒ์ผ ์‹œ์Šคํ…œ: ํด๋”์™€ ํŒŒ์ผ์˜ ๊ณ„์ธต ๊ตฌ์กฐ
  • ๐Ÿ‘ฅ ์กฐ์ง๋„: ํšŒ์‚ฌ๋‚˜ ํŒ€์˜ ๊ณ„์ธต ๊ตฌ์กฐ
  • ๐Ÿ“‹ ๋ฉ”๋‰ด ์‹œ์Šคํ…œ: ์ค‘์ฒฉ๋œ ๋ฉ”๋‰ด ๊ตฌ์กฐ

๐ŸŽฏ ๊ฒฐ๋ก 

Composite ํŒจํ„ด์€ ๋ณต์žกํ•œ ํŠธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋‹จ์ˆœํ•˜๊ณ  ์ผ๊ด€๋œ ๋ฐฉ์‹์œผ๋กœ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ํŠนํžˆ ๊ณ„์ธต์  ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„ ์‹œ์Šคํ…œ์—์„œ ์ฝ”๋“œ์˜ ๋ณต์žก์„ฑ์„ ํฌ๊ฒŒ ์ค„์ด๊ณ  ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŒจํ„ด์„ ์ ์šฉํ•  ๋•Œ๋Š” ๋‹จ์ผ ๊ฐ์ฒด์™€ ๋ณตํ•ฉ ๊ฐ์ฒด ๊ฐ„์˜ ์ผ๊ด€๋œ ์ธํ„ฐํŽ˜์ด์Šค ์„ค๊ณ„๊ฐ€ ํ•ต์‹ฌ์ด๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๊ณ  ํ™•์žฅ์„ฑ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

728x90
๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€