[Design Pattern] Composite Pattern

이은수, Lee EunSoo·2024년 10월 18일
0

DesignPattern

목록 보기
10/12
post-thumbnail

개요

Composite패턴은 우리가 많이 사용하는 컴퓨터의 파일관리자처럼 객체들을 트리 형태로 구조화 해서 데이터를 처리하는 패턴이다.

설명

의도

객체들을 트리 형태로 구조화 하여 그릇객체와 내용물객체를 동일하게 취급하기 위한 패턴

예제코드

역할

  • Component
    • Leaf와 Composite의 상위 클래스, 이들을 동일하게 취급 할 수 있도록 공통 인터페이스를 선언한다.
  • Composite
    • 그릇을 나타내는 역할
  • Leaf
    • 내용물에 해당하는 역할
    • 여기에는 자식 component가 올 수 없음
  • Client
    • Composite패턴의 이용자 역할, Main클래스

Java

public abstract class Entry {
    private Entry parent;

    // 부모를 설정한다
    protected void setParent(Entry parent) {
        this.parent = parent;
    }

    // 이름을 가져온다 
    public abstract String getName();

    // 크기를 가져온다 
    public abstract int getSize();

    // 목록을 표시한다 
    public void printList() {
        printList("");
    }

    // prefix를 앞에 붙여 목록을 표시한다
    protected abstract void printList(String prefix);

    // 문자열 표시
    @Override
    public String toString() {
        return getName() + " (" + getSize() + ")";
    }

    // 전체 경로를 가져온다 
    public String getFullName() {
        StringBuilder fullname = new StringBuilder();
        Entry entry = this;
        do {
            fullname.insert(0, entry.getName());
            fullname.insert(0, "/");
            entry = entry.parent;
        } while (entry != null);
        return fullname.toString();
    }
}

import java.util.ArrayList;
import java.util.List;

public class Directory extends Entry {
    private String name;
    private List<Entry> directory = new ArrayList<>();

    public Directory(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        int size = 0;
        for (Entry entry: directory) {
            size += entry.getSize();
        }
        return size;
    }

    @Override
    protected void printList(String prefix) {
        System.out.println(prefix + "/" + this);
        for (Entry entry: directory) {
            entry.printList(prefix + "/" + name);
        }
    }

    public Entry add(Entry entry) {
        directory.add(entry);
        entry.setParent(this);
        return this;
    }
}

public class File extends Entry {
    private String name;
    private int size;

    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    protected void printList(String prefix) {
        System.out.println(prefix + "/" + this);
    }
}

public class Main {
    public static void main(String[] args) {
        Directory rootdir = new Directory("root");

        Directory usrdir = new Directory("usr");
        rootdir.add(usrdir);

        Directory youngjin = new Directory("youngjin");
        usrdir.add(youngjin);

        File file = new File("Composite.java", 100);
        youngjin.add(file);
        rootdir.printList();

        System.out.println();
        System.out.println("file = " + file.getFullName());
        System.out.println("youngjin = " + youngjin.getFullName());
    }
}

실행 결과

Swift

class Entry: CustomStringConvertible{
    private var parent: Entry?
    
    init(){}
    
    func getName() -> String{return ""}
    func getSize() -> Int{return 0}
    func printList(_ prefix: String){}
    
    func setParent(_ parent: Entry){
        self.parent = parent
    }
    func printList(){
        printList("")
    }
    
    //toString
    var description: String {
        return "\(getName()) (\(getSize()))"
    }
    
    func getFullName() -> String{
        var tempStr: String = ""
        var tmpEntry: Entry? = self
        repeat {
            if let e = tmpEntry {
                tempStr = "/" + e.getName() + tempStr
                tmpEntry = e.parent
            }
        } while tmpEntry != nil
        return tempStr
    }
}

CustomStringConvertible 프로토콜

  • java의 toString처럼 print할때 출력되는 설정을 하기 위한 프로토콜

class Directory: Entry{
    private var name: String
    private var directory = Array<Entry>()
    
    init(_ name: String){
        self.name = name
    }
    
    override func getName() -> String {
        return name
    }
    override func getSize() -> Int {
        var size: Int = 0
        
        for i in directory{
            size = size + i.getSize()
        }
        return size
    }
    override func printList(_ prefix: String){
        print(prefix + "/" + self.name)
        for entry in directory{
            entry.printList(prefix + "/" + name)
        }
    }
    
    func add(_ entry: Entry) -> Entry{
        directory.append(entry)
        entry.setParent(self)
        return self
    }
    
    
}

class File: Entry {
    private var name: String
    private var size: Int
    
    init(_ name: String,_ size: Int) {
        self.name = name
        self.size = size
    }
    
    override func getName() -> String {
        return name
    }
    
    override func getSize() -> Int {
        return size
    }
    
    override func printList(_ prefix: String) {
        print(prefix + "/" + self.name)
    }
}

@main
struct Main {
    static func main() {
        let rootdir: Directory = Directory("root")
        let usrdir: Directory = Directory("usr")
        let youngjin: Directory = Directory("youngjin")
        
        rootdir.add(usrdir)
        usrdir.add(youngjin)
        
        let file: File = File("Composite.swift", 100)
        
        youngjin.add(file)
        rootdir.printList()
        
        print()
        print("file = \(file.getFullName())")
        print("youngjin = \(youngjin.getFullName())")
        
    }
}

결과

Entry클래스의 형태 고민

자바의 경우 추상클래스 였으나 swift로 구현하면서 프로토콜과 일반 클래스 사이에서 고민했는데 swift의 프로토콜의 경우 저장프로퍼티를 허용하지 않고 구현또한 extension이라는 확장기능을 이용해야 해서 그냥 클래스로 구현하였다.

오버 라이딩 되어야 되는 부분은 그냥 0 또는 공백문자를 반환하는 형태로 만들었다.

정리

tree형태로 객체를 다루어야 하는 상황이 온다면 나름 유용한 데이터 형태가 될 수 있다고 생각된다.

profile
iOS 개발자 취준생, 천 리 길도 한 걸음부터

0개의 댓글