Composite 패턴

5.디자인패턴 2016. 9. 17. 20:17

Composite 패턴은 "복합 객체(Composite Object)"와 "단일 객체(Leaf Object)"를 동일하게 취급하고 싶을 때 사용하는 패턴이다.

일반적으로 가장 이해하기 쉬운 비유는 디렉토리와 파일의 관계이다. 디렉토리는 복합 객체와 유사하고, 파일은 단일 객체와 유사한 특성을 가지고 있다. 디렉토리는 자기 내부에 같은 디렉토리 또는 파일을 가지고 있을 수 있다. 파일은 단일 객체이기 때문에 내부에 다른 객체를 가지고 있을 수 없다. 만약 디렉토리 - 파일 관계를 보다 단순화 시키고자 한다면 디렉토리와 파일을 동일한 타입으로 취급하는 방법을 사용할 수 있을 것이다.

물론 모든 동작들을 이와 같이 만들 수는 없다. 적어도 디렉토리와 파일이 유사한 동작을 할 수 있는 경우에만 가능하다. 아래의 예제에서는 파일의 갯수를 세는 동작과 파일의 라인 수(공백을 제외한)를 세는 동작에 대해서 정의하고 있다. 디렉토리는 파일이 아니고, 내부에 파일들을 가지고 있기 때문에 파일 개수를 세는 동작을 단일 객체인 파일에 위임할 수 있다. 또한 라인 수를 계산하는 동작에 대해서도 디렉토리는 항상 파일에게 이를 위임한다. 파일은 파일 갯수에 대해 물어보면 항상 1이라고 대답한다. 라인 수에 대한 응답은 파일을 열어 공백을 제외한 라인의 수를 제공하도록 한다. 만약 디렉토리와 파일이 동일한 상위 타입을 가지고 있다면 외부에서는 최 상위 디렉토리에게 파일의 수와 라인 수를 물어보는 것만으로도 전체 디렉토리 구조 상에서의 정보들을 얻어 낼 수 있을 것이다. 이런 복잡한 동작을 단순하게 만들어 줄 수 있는 것이 Composite 패턴이다.


Composite 패턴 다이어그램

Item 인터페이스는 디렉토리와 파일로부터 파일 갯수(getFiles()) 및 라인 수(getLines())를 얻어 올 수 있는 공통 인터페이스를 정의한다. 그리고 디렉토리를 구현한 DirectionItem과 파일을 구현한 FileItem은 외부에서 동일하게 취급될 수 있도록 Item 인터페이스를 구현한다. FileItem은 단일 객체로써, 자신의 파일 수( = 항상 1)와 라인 수를 계산하여 넘겨주도록 구현된다. DirectoryItem은 복합 객체로서 내부에 하위 디렉토리 및 하위 파일들을 담을 수 있는 List 를 소유한다.

DirectoryTree 클래스는 실제 디렉토리를 방문하면서 디렉토리 및 파일의 트리 구조를 만들고 최종 결과를 얻어 낼 수 있는 인터페이스를 가지고 있다. 이를 위해서 DirectoryTree 객체는 makeTree() 메소드를 통해서 Item 객체들을 생성하고 디렉토리 구조를 만드는 작업을 수행한다.

아래는 위의 클래스 다이어그램을 구현한 코드이다.


Composite 패턴의 구현

interface Item{

    public int getFiles();

    public int getLines();

}

class DirectoryItem implements Item{

    List<Item> children = new ArrayList<Item>();

    public void addChild(Item child){ children.add(child); }

    File file;

    public DirectoryItem(File file){

        this.file = file;

    }

    public int getFiles(){

        int result = 0;

        for(Item each : children){

            result += each.getFiles();

        }

        return result;

    }

    public int getLines(){

        int result = 0;

        for(Item each : children){

            result += each.getLines();

        }

        return result;

    }

}

class FileItem implements Item{

    File file;

    public FileItem(File file){ this.file = file; }

    public int getFiles(){

        return 1;

    }

    public int getLines(){

        Scanner scan;

        int result = 0;

        try {

        scan = new Scanner(file);

        while(scan.hasNextLine()){

            String line = scan.nextLine();

            if(!line.equals("")) result ++;

        }

        } catch (FileNotFoundException e) {

            System.out.println("File Not Found");

        }

        return result;

    }

}


Composite 패턴을 이용하여 디렉토리 트리를 만드는 DirectoryTree 클래스

class DirectoryTree{

    String path;

    public DirectoryTree(String path){ this.path = path; }

    Item items;

    public void makeTree(){

        File file = new File(path);

        items = tourDirectory(file);

    }

    private Item tourDirectory(File file){

        DirectoryItem directory = new DirectoryItem(file);

        File[] files = file.listFiles();

        for(File each : files){

            if(each.isDirectory()){

                Item childDirectory = tourDirectory(each);

                directory.addChild(childDirectory);

            }

            else{

                FileItem childFile = new FileItem(each);

                directory.addChild(childFile);

            }

        }

        return directory;

    }

    public int getFiles(){

        return items.getFiles();

    }

    public int getLines(){

        return items.getLines();

    }

} 


사용 방법

public static void main(String[] args) {

    DirectoryTree treenew DirectoryTree("C:/JavaDesignPattern/src/GofPattern/");

    tree.makeTree();

    System.out.println(tree.getFiles());

    System.out.println(tree.getLines());

} 


여러 다른 타입의 객체들을 동일한 타입으로 취급할 수 있게 되면 구현은 단순해지기 마련이다. Composite 패턴에서는 복합 객체와 단일 객체가 동일하게 취급된다. 그리고 자칫 복잡해질 수도 있는 복합 객체가 하위 객체들에게 작업을 위임하는 방식으로 동작함으로써 구현이 단순해졌음을 알 수 있다. 

'5.디자인패턴' 카테고리의 다른 글

Flyweight 패턴  (0) 2016.09.18
Chain Of Responsibility 패턴  (0) 2016.09.17
Iterator 패턴  (0) 2016.09.17
Enum Abstract Factory 패턴  (0) 2016.09.16
Proxy 패턴과 그 활용  (0) 2016.09.16
Posted by 이세영2
,

Iterator 패턴

5.디자인패턴 2016. 9. 17. 10:55

Java에서는 이제 언어적인 지원이 이루어지기 때문에 잘 쓰이지는 않지만 여전히 C++ 등에서는 자주 쓰이는 패턴이다. 보통 컬렉션들(C++에서는 컨테이너)에 쓰인다.

Iterator 패턴은 일련의 순서를 가진 데이터 집합에 대하여 순차적인 접근을 지원하는 패턴이다.


Iterator 패턴 클래스 다이어그램


Iterator 패턴의 구현

interface IIterator{

    public boolean hasNext();

    public Object next();

}

interface IContainer{

    public IIterator iterator();

}

class NameContainer implements IContainer{

    List<String> list = new ArrayList<String>();

    public void add(String name){

        list.add(name);

    }

    class NameIterator implements IIterator{

        String current;

        @Override

        public boolean hasNext() {

            if(list.size() == 0) return false;

            if(list.indexOf(current) != list.size()-1) return true;

            return false;

        }

        @Override

        public Object next() {

            if(list.size() == 0) return null;

            if(list.indexOf(current) != list.size()-1){

                current = list.get(list.indexOf(current) + 1);

                return current;

            }

            return null;

        }

    }

    @Override

    public IIterator iterator() {

        return new NameIterator();

    }

}


사용방법

public static void main(String[] args) {

    NameContainer names = new NameContainer();

    names.add("Nick");

    names.add("Ceasar");

    names.add("Augustus");

    for(IIterator iter = names.iterator(); iter.hasNext();){

        String name = (String)iter.next();

        System.out.println(name);

    }

}



우선 데이터 집합을 가지고 있는 Container는 모두 Iterator를 제공해 줄 수 있어야 한다. 따라서 Container의 인터페이스인 IContainer와 Iterator의 인터페이스인 IIterator를 선언한다. 이는 어떤 Container를 구현하더라도 같은 방식으로 Iterator를 사용할 수 있도록 하기 위함이다. 구체 클래스들은 선언한 인터페이스들을 구현한다.

Iterator가 직접 데이터에 접근할 수 있도록 하는 것이 구현이 단순해 지므로 보통 Iterator는 Container의 내부 클래스로 구현하는 경우가 많다. 위의 예제에서도 NameContainer 내부에 NameIterator를 선언하였다. NameContainer로부터 NameIterator를 받아 오는 메소드는 iterator()인데, 이 메소드가 호출되면 NameContainer를 새로 생성하여 넘겨준다. 이렇게 해야 Iterator를 가지고 데이터에 접근하는 쓰레드가 많아도 문제 없이 동작이 가능하다.(데이터 집합의 추가/삭제로 인한 쓰레드 문제는 별도로 처리해 주어야 한다.)

'5.디자인패턴' 카테고리의 다른 글

Chain Of Responsibility 패턴  (0) 2016.09.17
Composite 패턴  (0) 2016.09.17
Enum Abstract Factory 패턴  (0) 2016.09.16
Proxy 패턴과 그 활용  (0) 2016.09.16
Observer 패턴  (0) 2016.09.16
Posted by 이세영2
,

미리 봐야 할 것 : Abstract Factory 패턴

Abstract Factory 패턴의 경우도 Factory Method 패턴과 마찬가지로 Factory 객체 생성의 이슈가 있다. 가만히 놔두면 Factory 객체가 자주 생성될 수 있고, 이를 해결하려면 Singleton 패턴을 적용해 주어야 한다. 이러한 문제를 해결하는 방법으로 enum을 활용한 방법이 있다. 이를 Enum Abstract Factory 패턴이라고 한다.


우선 생성하고자 하는 대상을 알아보자. Abstract Factory 패턴은 일련의 연관성 있는 객체 군을 생성하는 패턴이다. Xp 객체 군과 Linux 객체군이 있다고 가정하자. 그리고 각 객체군은 Button과 Edit 객체를 포함하고 있다. 마지막으로 생성된 Button과 Edit 객체는 객체군과 상관 없이 동일하게 취급 되어야 한다. 따라서 IButton과 IEdit와 같이 인터페이스를 선언해 주고 Xp 타입과 Linux 타입에 해당하는 클래스들을 선언해 준다.


생성하고자 하는 객체들의 선언

interface IButton {};

interface IEdit   {};

class XpEdit implements IEdit {

    public XpEdit(){System.out.println("XpEdit()");}

};

class XpButton implements IButton {

    public XpButton(){System.out.println("XpButton()");}

};

class LinuxEdit implements IEdit {

    public LinuxEdit(){System.out.println("LinuxEdit()");}

};

class LinuxButton implements IButton {

    public LinuxButton(){System.out.println("LinuxButton()");}

};


이제 Abstract Factory를 enum을 이용해서 구현한다.


Enum Abstract Factory의 구현

interface IFactory

{

    public IButton createButton();

    public IEdit createEdit();

};


public enum EnumAbstractFactory implements IFactory{

    XP{

        public IButton createButton() {

            return new XpButton();

        }

        public IEdit createEdit() {

            return new XpEdit();

        }

    }

    ,LINUX{

        public IButton createButton() {

            return new LinuxButton();

        }

        public IEdit createEdit() {

            return new LinuxEdit();

        }

    }

    ;

}

각 Factory들도 사용하는 측에서는 동일한 타입으로 취급이 가능해야 한다. 따라서 Factory들에 대한 추상 타입인 IFactory를 선언해 준다. 그리고 개별 Factory들이 IFactory를 구현하도록 선언해 준다. enum은 실제로 abstract class이기 때문에 인터페이스를 구현하는 것도 가능하다.


사용 방법

public static void main(String[] args) {

    IFactory factory = EnumAbstractFactory.XP;

    IButton button = factory.createButton();

    IEdit edit = factory.createEdit();

    factory = EnumAbstractFactory.LINUX;

    button = factory.createButton();

    edit = factory.createEdit();

}

사용 방법은 일반적인 enum과 동일하다. 대신 Factory 객체의 생성 문제가 enum을 통해 해결되었다. enum의 각 하위 타입은 public static final 키워드를 암시적으로 달고 있다. 따라서 Singleton의 특성을 가지기 때문에 별도로 생성할 필요가 없고, 오직 한번만 생성이 된다.

'5.디자인패턴' 카테고리의 다른 글

Composite 패턴  (0) 2016.09.17
Iterator 패턴  (0) 2016.09.17
Proxy 패턴과 그 활용  (0) 2016.09.16
Observer 패턴  (0) 2016.09.16
Abstract Factory 패턴  (0) 2016.09.16
Posted by 이세영2
,