'Pattern'에 해당되는 글 7건

  1. 2009.10.09 [JAVA] Pattern, Matcher (6)
  2. 2008.05.26 [Java-Pattern] Prototype
  3. 2008.05.26 [Java-Pattern] Builder
  4. 2008.05.26 [Java-Pattern] Bridge
  5. 2008.05.26 [Java-Pattern] Visitor
  6. 2008.05.26 [Java-Pattern] Chain of Responsibility
  7. 2008.05.26 [Java-Pattern] Mediator

[JAVA] Pattern, Matcher

Java 2009.10.09 14:38
저작자 표시 비영리 변경 금지
신고
Posted by 용식
 

인스턴스를 생설할때 new를 사용하는 것이 아니라 이미 만들어진 인스턴스를 복사해서


새로운 인스턴스를 만들어 내는 패턴이다. 이 인스턴스 생성 방식을 사용 하는 이유는


1. 종류가 너무 많아 한개의 클래스로 할 수 없는 경우

2. 클래스로부터 인스턴스를 생성하기 어려운 경우

3. 프레임워크와 생성할 인스턴스를 분리하고 싶은 경우


정도로 나눌 수 있다.


예를 보면..

Product 인터페이스와 Manager 클래스는 framework패키지에 속해있고, 인스턴스를 복제하는 일을 한다.


Manager클래스는 createClone을 호출하지만 구체적의 어느 클래스의 인스턴스인지 까지는 알지 못 하며 단지 Product인터페이스를 구현한 클래스의 인스턴스라면 복제가 가능하다.


MessageBox클래스와 UnderlinePen클래스는 둘 다 Product 인터페이스를 구현하고 있으며, 이들의 인스턴스를 만들어 Manager클래스에 등록해두면 언제든지 복제 할 수 있다.


여기서 사용된 복제는 clone()메소드를 사용하였으며, java.lang.Cloneable인터페이스를 구현하고 있는 클래스의 인스턴스는 clone메소드를 사용해서 자동적으로 자기자신을 복제 할 수 있다.



코드를 보면

1. Product인터페이스

package framework;

public interface Product extends Cloneable {
    public abstract void use(String s);
    public abstract Product createClone();
}

2.Manager클래스

package framework;
import java.util.*;

public class Manager {
    private Hashtable showcase = new Hashtable();
    public void register(String name, Product proto) {
        showcase.put(name, proto);
    }
    public Product create(String protoname) {
        Product p = (Product)showcase.get(protoname); //Upcasting
        return p.createClone();
    }
}

이름과 인스턴스의 대응관계를 해쉬 테이블을 사용해 저장하며, create에서는 Product 인터페이스를 구현한 클래스의 인스턴스를 복사하여 return해주고 있다.


3.MessageBox

String을 주어진 char형으로 감싸 출력하는 클래스이다.

import framework.*;

public class MessageBox implements Product {
    private char decochar;
    public MessageBox(char decochar) {
        this.decochar = decochar;
    }
    public void use(String s) {
        int length = s.getBytes().length;
        for (int i = 0; i < length + 4; i++) {
            System.out.print(decochar);
        }
        System.out.println("");
        System.out.println(decochar + " "  + s + " " + decochar);
        for (int i = 0; i < length + 4; i++) {
            System.out.print(decochar);
        }
        System.out.println("");
    }
    public Product createClone() {
        Product p = null;
        try {
            p = (Product)clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }

}

Product 인터페이스만 구현되어 있지만, Product인터페이스는 java.lang.Cloneable인터페이스를 확장한 것이기 때문에 clone()사용이 가능하다. 또한 clone()메소드는 자신의 클래스(및 하위 클래스)에서만 호출할 수 있기 때문이 (Protected형) 다른 클래스의 요청으로 복제하는 경우에는

다른 메소드로 clone을 감싸줄 필요가 있다.


4.UnderlinePen클래스

주어진 캐릭터로 String에 밑줄을 그어 출력하는 클래스이다.

import framework.*;

public class UnderlinePen implements Product {
    private char ulchar;
    public UnderlinePen(char ulchar) {
        this.ulchar = ulchar;
    }
    public void use(String s) {
        int length = s.getBytes().length;
        System.out.println("\""  + s + "\"");
        System.out.print(" ");
        for (int i = 0; i < length; i++) {
            System.out.print(ulchar);
        }
        System.out.println("");
    }
    public Product createClone() {
        Product p = null;
        try {
            p = (Product)clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }

}


Main클래스(테스트를 위함)

import framework.*;

public class Main {
    public static void main(String[] args) {
        // 준비
        Manager manager = new Manager();
        UnderlinePen upen = new UnderlinePen('~');
        MessageBox mbox = new MessageBox('*');
        MessageBox sbox = new MessageBox('/');
        manager.register("strong message", upen);
        manager.register("warning box", mbox);
        manager.register("slash box", sbox);

        // 생성
        Product p1 = manager.create("strong message");
        p1.use("Hello, world.");
        Product p2 = manager.create("warning box");
        p2.use("Hello, world.");
        Product p3 = manager.create("slash box");
        p3.use("Hello, world.");
    }
}

이제 하나를 잡아서 따라가보자..

Main에서 최초로 Manager manager = new Manager();
 클래스를 생성했고,
UnderlinePen upen = new UnderlinePen('~');

upen이란 이름으로 UnderlinePen클래스의 인스턴스를 생성해주고 있다.


그리고,

manager.register("strong message", upen);
Manager클래스의 register 메소드를 사용하여 해쉬 테이블에 strong message라는 이름으로

upen인스턴스를 연결시켜 주고있다.


Product p1 = manager.create("strong message");
Product형의 p1은 manager인스턴스의 create메소드를 사용하여 그 값을 받는데

create("strong message")를 살펴보면


public Product create(String protoname) {
        Product p = (Product)showcase.get(protoname); //Upcasting
        return p.createClone();
    }

아까 register를 사용하여 해쉬테이블에 등록한 인스턴스들 중 protoname즉 strong message라는 이름을 가진 것을 꺼내어 Product p에 대입시키고 있으며(Product형으로 Upcasting)

, 이 p의 createClone()을 실행

시킨 후 그 리턴값을 리턴해주고 있다.


crateClone()을 보면

    public Product createClone() {
        Product p = null;
        try {
            p = (Product)clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }

(Product)clone(); 자기 자신을 복사하여 (Product형으로 Upcastiong된 UnderlinePen클래스의 인스턴스) 그 리턴값을 Prodcut형으로 DownCasting 해주고(clone의 리턴형이 object형이므로)

그 값을 리턴해준다. 즉 Product형으로 형변환된 복제된 UnderlinePen클래스의 인스턴스가 리턴되는 것이다.

 

그래서 Main에서 Product p1 = manager.create("strong message");

 

이 문장에서 p1에는 Product형으로 형변환된 복제된 UnderlinePen클래스의 인스턴스가 대입되게 되고 p1.use를 실행 하면


 p1.use("Hello, world.");

Hello,world밑에 밑줄이 그어져서 출력되게 된다.

신고
Posted by 용식

문서작성 프로그램을 예제로..


Builder라는 추상 클래스를 만들고 여기에 추상메서드

makeTitle,makeString,makeItems,getResult라는 메서드를 둔다..


그리고 Builder를 상속하는 TextBuilder,HTMLBuilder 클래스를 만들고


위 추상 메서드들을 구현하여 준다.


이름만 봐도 알겠지만


하나는 텍스트로 만들어서 뿌려주는 클래스고 하나는 html형식으로 파일을 만들어주는 클래스이다.



그리고 클라이언트쪽에서는 저 Builder를 직접 호출하는 것이 아니라


Director라는 클래스를 통해서 호출한다.


Director클래스는


public class Director {
    private Builder builder;
    public Director(Builder builder) {      // Builder의 하위 클래스의 인스턴스가 제공되기 때문에
        this.builder = builder;             // builder 필드에 보관해 둔다.
    }
    public Object construct() {             // 문서 구축
        builder.makeTitle("Greeting");              // 타이틀
        builder.makeString("아침과 낮에");     // 문자열
        builder.makeItems(new String[]{             // 항목
            "좋은 아침입니다.",
            "안녕하세요",
        });
        builder.makeString("밤에");                 // 다른 문자열
        builder.makeItems(new String[]{             // 다른 항목
            "안녕하세요",
            "안녕히 주무세요",
            "안녕히 계세요",
        });
        return builder.getResult();                 // 완성된 문서가 반환 값이 된다.
    }
}

이렇게 구현되어있다. builder클래스에 정의되어있는 API를 사용하여

원하는 작업을 하는 것이다. 물론 Director클래스는 자기가 사용하고 있는 API가 TextBuilder의 것인지 HTMLBuilder의 것인지 알지 못 한다.. (독립적이다..)


Builder를 상속하는 하위클래스를 만들어 얼마든지 기능을 확장 할 수 있으며


클라이언트 입장에서는 단지


Director director = new Director(new TextBuilder());
            String result = (String)director.construct();
            System.out.println(result);


이런식으로 director를 통해서 Builder를 상속한 하위클래스를 넘겨주기만 하면 된다.


신고
Posted by 용식
 

이 패턴은 기능의 클래스 계층과 구현의 클래스 계층으로 나누어 설계하는 것이다.

기능의 클래스 계층이란

Something이란 클래스가 있을때 여기에 새로운 기능을 추가하기 위해서

Something의 하위 클래스로서 SomethigGood이라는 클래스를 만들어 기능을 확장할때

이것을 기능의 클래스 계층이라 한다.


구현의 클래스 계층이란

A라는 추상메서드를 상속하는 B클래스에서 그 추상메서드에서 선언된 메서드들을 실제로 구현하게 되는데 이를 구현의 클래스 계층이라고 한다.


예제프로그램

여기서는 주어진 문자열을 박스안에 넣어서 출력하는 클래스 하나와

주어진 숫자만큼 반복해서 출력하는 프로그램을 예제로 사용하였다.



기능의 클래스 계층 - Display(표시하는 클래스),CountDisplay(지정 횟수만큼 표시하는 클래스)

구현의 클래스 계층 - DisplayImpl(표시하는 클래스), StringDisplayImpl(문자열을 사용해서 표시하는 클래스)


public class Display {
    private DisplayImpl impl;
    public Display(DisplayImpl impl) {
        this.impl = impl;
    }
    public void open() {
        impl.rawOpen();
    }
    public void print() {
        impl.rawPrint();
    }
    public void close() {
        impl.rawClose();
    }
    public final void display() {
        open();
        print();                   
        close();
    }
}
- 무언가를 표시하기 위한 클래스로 생성자에게는 구현되는 클래스의 인스턴스를 넘겨주고

이를 필드에 저장하여 앞으로의 처리에 사용된다.

Display의 메서드들을 살펴보면 전부 DisplayImpl의 메서드로 변환되어 있다. display메서드는

open,print,close를 사용해여 '표시한다

라는 처리를 실현하고 있다.


기능의 클래스계층 : CountDisplay

public class CountDisplay extends Display {
    public CountDisplay(DisplayImpl impl) {
        super(impl);
    }
    public void multiDisplay(int times) {       // times번을 반복해서 표시한다.
        open();
        for (int i = 0; i < times; i++) {
            print();
        }
        close();
    }
}
Display를 상속하며, 표시한다라는 기능밖에 없던 Display의 기능을 지정횟수만큼 표시한다 라는 기능으로 확장 시킨 클래스이다. Display에서 상속받은 open,print,close를 사용하여 새로운 메서드를 구현하고 있다.



구현의 클래스 계층 - DisplayImpl


public abstract class DisplayImpl {
    public abstract void rawOpen();
    public abstract void rawPrint();
    public abstract void rawClose();
}

구현의 클래스 계층의 최상위 클래스로서 앞에 Display의 open,close,print에 대응하는

rawOpen,rawPrint,rawClose 메서드들을 가지고 있다.


구현의 클래스 계층 - StringDisplayImpl


public class StringDisplayImpl extends DisplayImpl {
    private String string;                              // 표시해야할 문자열
    private int width;                                  // 바이트 단위로 계산한 문자열의 "길이"
    public StringDisplayImpl(String string) {           // 생성자에서 넘어온 문자열 string를
        this.string = string;                               // 필드에 기억해 둔다.
        this.width = string.getBytes().length;              // 그리고 바이트 단위의 길이도 필드에 기억해두고 나중에 사용한다.
    }
    public void rawOpen() {
        printLine();
    }
    public void rawPrint() {
        System.out.println("|" + string + "|");         // 앞뒤에 "|"를 붙여서 표시
    }
    public void rawClose() {
        printLine();
    }
    private void printLine() {
        System.out.print("+");                          // 틀의 모퉁이를 표현하는 "+"마크를 표시한다.
        for (int i = 0; i < width; i++) {               // width개의 "-"를 표시해서
            System.out.print("-");                      // 틀의 선으로 이용한다.
        }
        System.out.println("+");                        // 틀의 모퉁이를 표현하는 "+"마크를 표시한다.
    }
}
DisplayImpl 클래스를 상속하며 상위 클래스에서 정의한 추상 메서드들을 실제적으로 구현하고 있다.


Main

public class Main {
    public static void main(String[] args) {
        Display d1 = new Display(new StringDisplayImpl("Hello, Korea."));
        Display d2 = new CountDisplay(new StringDisplayImpl("Hello, World."));
        CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello, Universe."));
  RandomCountDisplay d4 = new RandomCountDisplay(new StringDisplayImpl("hi,,"));
  d4.randomDisplay(10);
        d1.display();
        d2.display();
        d3.display();
        d3.multiDisplay(5);
    }
}

d3를 예제로 따라가보면..

CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello, Universe."));


 

우선 StringDisplayImple의 인스턴스를 생성하여 CountDisplay의 생성자 인자로 넘겨주고 있다.

CountDisplay의 생성자는

public CountDisplay(DisplayImpl impl) {
        super(impl);
    }

로 되어있고, super를 사용해 전달받은 인스턴스(DisplayImpl형으로 업캐스팅 된 StringDisplayImpl의 인스턴스)를 상위 클래스에게 넘겨주고 있다. 상위클래스는 Display는

public Display(DisplayImpl impl) {
        this.impl = impl;
    }

로 구성되어 있으며 (생성자가..) 필드에 impl를 보존해 두고 사용하게 된다.


즉 전달받은 impl은 Display와 CounstDisplay의 메서드를 모두 사용 할 수 있는 것이다.


d3.display();
d3.multiDisplay(5);


또한 d3는 CountDisplay형이기 때문에 상속받은 메서드인 display와 기능확장으로 구현한 multiDisplay메서드를 모두 사용 할 수 있다. 이 메서드들은 open,print,close 메서드들로 구성이 되고 이 메서드들은 impl.rawOpen,등의 메서드로 구성이 되는데


display 메서드를 살펴보면 open,print,close로 되어 있으며 이는

각각 rawOpen등을 호출하고 있다. 아까 보존한 impl(DisplayImpl형으로 업캐스팅 된 StringDisplayImpl의 인스턴스) 의 rawOpen등을 호출하게 되는데,


rawOpen은 DisplayImpl의 추상메서드이고 이는 StringDisplayImpl에서 구현하고 있으므로

업캐스팅 원리에 의해 StringDisplayImpl에서 구현한 rawOpen이 실행되는 것이다.


(만약 전달받은 인스턴스가 FileDisplayImpl의 인스턴스라한다면 당연히 이 클래스의 메서드가 실행되는 것..)


multiDisplay같은 경우 Display형인 d1,d2등은 호출을 할 수 없다.


왜냐하면 Display형에서 multiDisplay를 구현 혹은 제시하지 않고 있기 때문에 ...


알 수 없는 메서드라고 나오는 것이다.


PS- 여기서 랜덤 횟수만큼 표시하고 싶다. 이것은 기능에 해당한다. 어떻게 표시할 것인가 에 해당..따라서 이런 경우 CountDisplay를 상속하여 기능을 확장하면 되고.


만일 파일에서 읽어들여 표시하고 싶다 라고 한다면 이것은 구현에 해당되므로

DisplayImpl를 상속하는 확장 클래스를 하나 더 만들어 주면 될 것이다.

신고
Posted by 용식
 

Composite 패턴과 조금 유사한 형태이다. 데이터 구조와 처리를 분리시킨 패턴으로 데이터 구조안을 돌아다니는 주체인 방문자를 나타내는 클래스를 준비해서 그 클래스에게 처리를 맡기는 패턴이다. 새로운 처리를 추가하고 싶을때는 새로운 방문자를 만들면 되는 것. 그리고 데이터 구조는 문을 두드리는 방문자를 받아들이면 된다.


디렉토리 구조를 다시 예제 프로그램으로..

8개의 클래스로 분류된다.

1.Visitor - 파일이나 디렉토리를 방문하는 방문자를 나타내는 추상 클래스

2.Acceptor - Visitor 클래스의 인스턴스를 받아들이는 데이터 구조를 나타내는 인터페이스

3.ListVisit - Visitor의 하위 클래스로 파일이나 디렉토리의 일람을 나타내는 클래스

4.Entry - 파일과 디렉토리의 상위 클래스가 되는 추상 클래스 (Acceptor 인터페이스 구현)

5.File - 파일을 나타내는 클래스

6.Directory - 디렉토리를 나타내는 클래스

7.FileTreatmentException - 파일에 대해서 add한경우 발생하는 예외 클래스

8.Main - 동작 테스트용 클래스



Visitor Class

public abstract class Visitor {
    public abstract void visit(File file);
    public abstract void visit(Directory directory);
}

visit이라는 메소드가 오버로드 되어있다. 이 메소드는 하위 클래스에서 각각의 인스턴스에 대해

알맞은 처리를 하도록 구현된다.


Acceptor Interface

public interface Acceptor {
    public abstract void accept(Visitor v);
}

방문자를 받아들이는 인터페이스로, 이를 구현한 클래스(File,Directory)의 인스턴스들은 이 메소드를 사용하여 방문자를 받아들인다.


Entry Class

import java.util.Iterator;

public abstract class Entry implements Acceptor {
    public abstract String getName();                                   // 이름을 얻는다.
    public abstract int getSize();                                      // 사이즈를 얻는다.
    public Entry add(Entry entry) throws FileTreatmentException {       // 엔트리를 추가
        throw new FileTreatmentException();
    }
    public Iterator iterator() throws FileTreatmentException {    // Iterator의 생성
        throw new FileTreatmentException();
    }
    public String toString() {                                          // 문자열 표현
        return getName() + " (" + getSize() + ")";
    }
}

Visitor 패턴에 적용시킬 수 있게 Acceptor 인터페이스를 구현하고 있으며, 이는 Entry의 하위클래스들이 acceptor()메소드를 구현하도록 한다. add메소드는 디렉토리에 대해서만 유효하기 때문에 디렉토리 클래스에서는 오버라이드 해주고, 파일 클래스에서는 상속된 이 메소드를 그대로 사용함으로서 파일에서 add가 일어날 경우 예외를 발생시키도록 해준다.


public class File extends Entry {
    private String name;
    private int size;
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }
    public String getName() {
        return name;
    }
    public int getSize() {
        return size;
    }
    public void accept(Visitor v) {
        v.visit(this);
    }
}

accept메소드가 드디어 나왔다. 인자는 Visitor이다. accept메소드를 받아서 visit메소드를 호출하는데 자기 자신을 인자로 넘기고 있다. 나중에 보충 설명을 하도록 하겠다.


Directory Class

import java.util.Iterator;
import java.util.Vector;

public class Directory extends Entry {
    private String name;                    // 디렉토리의 이름
    private Vector dir = new Vector();      // 디렉토리 엔트리의 집합
    public Directory(String name) {         // 생성자
        this.name = name;
    }
    public String getName() {               // 이름을 얻는다.
        return name;
    }
    public int getSize() {                  // 사이즈를 얻는다.
        int size = 0;
        Iterator it = dir.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry)it.next();
            size += entry.getSize();
        }
        return size;
    }
    public Entry add(Entry entry) {         // 엔트리의 추가
        dir.add(entry);
        return this;
    }
    public Iterator iterator() {      // Iterator의 생성
        return dir.iterator();
    }
    public void accept(Visitor v) {         // 방문자를 받아들임
        v.visit(this);
    }
}

accept메소드가 존재한다.


ListVisit Class

import java.util.Iterator;

public class ListVisitor extends Visitor {
    private String currentdir = "";                         // 현재 주목하고 있는 디렉토리명
    public void visit(File file) {                  // 파일을 방문했을 때 호출된다.
        System.out.println(currentdir + "/" + file);
    }
    public void visit(Directory directory) {   // 디렉토리를 방문했을 때 호출된다.
        System.out.println(currentdir + "/" + directory);
        String savedir = currentdir;
        currentdir = currentdir + "/" + directory.getName();
        Iterator it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry)it.next();
            entry.accept(this);
        }
        currentdir = savedir;
    }
}

visit메소드가 구현되어 있다. 여기까지만 보고 아~ 하는 사람도 있을지 모르겠다. currentdir필드는 현재 주목하고 있는 디렉토리 명이다. visit(File)메소드가 파일을 방문했을 때에 File클래스의 accept메소드 내에서 호출된다. 인수 file은 방문한 File 클래스의 인스턴스이다. accept메소드와 visit메소드는 서로 재귀적 호출 관계에 있는 것이다.


또한, visit(Directory)메소드를 보면 Iterator를 취득하여 각 엔트리에 대해서 accept메소드를 호출하고 있다. 이 accept메소드에서 다시 visit을 호출하고.... etc...


Main Class

Directory rootdir = new Directory("root");
            Directory bindir = new Directory("bin");
            Directory tmpdir = new Directory("tmp");
            Directory usrdir = new Directory("usr");
            rootdir.add(bindir);
            rootdir.add(tmpdir);
            rootdir.add(usrdir);
            bindir.add(new File("vi", 10000));
            bindir.add(new File("latex", 20000));
            rootdir.accept(new ListVisitor());             

일부분만 보았다.

우선 root-bin-vi,latex

           -tmp

           -usr

이렇게 되어있는 구조이다. 최초 rootdir의 accept를 호출하고 있으며, 인자는 생성한 Visitor이다. rootdir의 accept는 visit메소드를 호출하며 인자로 자기 자신을 넘긴다. visit메소드에서는

/root를 출력하고, iterator를 사용 root의 entry를 뒤져본다. bindir이 나오게 되며 while문 안에서 bindir의 accept를 호출하고, 다시 bindir에서는 visit을 호출하며, 위 처럼 동작을 반복한다.

/root/bin을 출력하고 다시 bin의 entry를 뒤져보고, 이번엔 파일 vi가 나오며, vi의 accept를 호출하고, 다시 visit이 호출되고, /root/bin/vi를 출력하고, 다시 bin을 뒤져서 latex가 나오고....

블라블라...


ListVisit메서드는 하나만 생성이 되며 Accept메소드가 호출될때 인자로 넘어가는 ListVisit의 인스턴스도 오로지 하나이다. (그래야 작업이 이어질것 아닌가..)

어느 계층에서건 entry가 발견이 되면 accept메서드를 호출하게 되며, accpet메서드에서 다시 visit메서드를 호출함으로서 자신에 대한 처리를 하고 , 다시 바톤을 accept를 호출했던 while문 쪽으로 넘기는 것이다. (다음 entry를 검사할 수 있게..)


이것이 장점은 기능의 확장이 쉽다는 것이다. 데이터구조와 로직을 완전히 분리 시켰기 때문이다. 한 작업에 대해 그 작업을 하는 ListVisit의 인스턴스는 딱 하나만 생성이 되는 것을 이용하여 다른 작업 (html파일찾기등)을 추가하고 싶을때에는 다른 수정 필요없이 다른 기능을 하는 Visitor클래스만 작성하면 되는 것이다.

신고
Posted by 용식
 

책임 떠넘기기 패턴

어떤 처리 요구가 들어왔을때, 그것을 처리 할 수 있는 인스턴스가 나타날때까지

그 처리를 넘기는 패턴이다.


예제프로그램은 임의의 숫자를 가진 트러블을 발생시키고, 트러블을 해결하는 클래스들을 종류별로

만들어 놓은 후 Chain of Responsibility패턴을 구현한 것이다.


1.Trouble - 발생한 트러블을 나타내는 클래스. 트러블 번호를 갖는다.

2.Support - 트러블을 해결하는 추상 클래스

3.NoSupport - 트러블을 해결하는 클래스 (항상 처리하지 않음.)

4.LimitSupport - 트러블을 해결하는 클래스

....


Trouble Class

public class Trouble {
    private int number;             // 트러블 번호
    public Trouble(int number) {    // 트러블의 생성
        this.number = number;
    }
    public int getNumber() {        // 트러블 번호를 얻는다.
        return number;
    }
    public String toString() {      // 트러블의 문자열 표현
        return "[Trouble " + number + "]";
    }
}

트러블을 나타내면 트러블 번호를 갖는다. 나중에 처리하는 클래스들은 이 번호를 가지고 자신이

처리 할 수 있는지의 여부를 결정한다.


Support Class

public abstract class Support {
    private String name;                    // 트러블 해결자의 이름
    private Support next;                   // 떠넘기는 곳
    public Support(String name) {           // 트러블 해결자의 생성
        this.name = name;
    }
    public Support setNext(Support next) {  // 떠넘길 곳을 설정
        this.next = next;
        return next;
    }
    public final void support(Trouble trouble) {  // 트러블 해결 순서
        if (resolve(trouble)) {
            done(trouble);
        } else if (next != null) {
            next.support(trouble);
        } else {
            fail(trouble);
        }
    }
    public String toString() {              // 문자열 표현
        return "[" + name + "]";
    }
    protected abstract boolean resolve(Trouble trouble); // 해결용 메소드
    protected void done(Trouble trouble) {  // 해결
        System.out.println(trouble + " is resolved by " + this + ".");
    }
    protected void fail(Trouble trouble) {  // 미해결
        System.out.println(trouble + " cannot be resolved.");
    }
}

next필드는 Support형으로 다음 문제를 해결할 클래스의 인스턴스를 가지고 있으며, 이는 setNext

메소드를 통해 설정 할 수 있다. resolve메소드는 하위 클래스에서 구현할 것을 상정한 추상 메소드입니다. 반환값이 true이면 해결한거고 아니면 해결 못 한 것..


NoSupport Class

public class NoSupport extends Support {
    public NoSupport(String name) {
        super(name);
    }
    protected boolean resolve(Trouble trouble) {     // 해결용 메소드
        return false; // 자신은 아무 처리도 하지 않는다.
    }
}

LimitSupprot Class

public class LimitSupport extends Support {
    private int limit;                              // 이 번호 미만이면 해결 할수 있다.
    public LimitSupport(String name, int limit) {   // 생성자
        super(name);
        this.limit = limit;
    }
    protected boolean resolve(Trouble trouble) {         // 해결용 메소드
        if (trouble.getNumber() < limit) {
            return true;
        } else {
            return false;
        }
    }
}


Support클래스를 상속하고 있는 실제 처리 클래스들은 다 이런식으로 구성되어있다.


Main..

public class Main {
    public static void main(String[] args) {
        Support alice   = new NoSupport("Alice");
        Support bob     = new LimitSupport("Bob", 100);
        Support charlie = new SpecialSupport("Charlie", 429);
        Support diana   = new LimitSupport("Diana", 200);
        Support elmo    = new OddSupport("Elmo");
        Support fred    = new LimitSupport("Fred", 300);
        // 연쇄의 형성
        alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
        // 다양한 트러블 발생
        for (int i = 0; i < 500; i += 33) {
            alice.support(new Trouble(i));
        }
    }
}

해결 인스턴스별로 각각 6개를 생성하고 연쇄를 형성한다. 이것은

alice인스턴스의 next필드에는 bob이

bob인스턴스의 next필드에는 charlie가..

이런식으로 들어가 있게 되는 것이고, 이것은 Support클래스에서


public final void support(Trouble trouble) {  // 트러블 해결 순서
        if (resolve(trouble)) {
            done(trouble);
        } else if (next != null) {
            next.support(trouble);
        } else {
            fail(trouble);
        }
여기서 사용된다. 예를 들어 Main클래스에서

alice.support(new Trouble(i));

로 최초 호출을 하고 있는데, 이는 alice의 resolve가 호출되고 alice는 무조건 false를 return하므로 , else문에 의해서 next.support(trouble)이 실행된다.

alice의 next는 bob이므로 bob.support와 같은 의미이고 이는 bob의 resolve를 호출하게 되는

이런 방식이다. (재귀적 호출)


즉, handler 클래스를 두고 이 클래스가 자기 자신을 호출하여 자기 필드에 있는 실제 처리 인스턴스들을 사용해 처리를 넘기는 것이다.


이패턴이 사용되지 않으면 요구를 하는 사람이 누가 처리할지 까지 알고 있어야 한다.

신고
Posted by 용식
 

조정자 역할을 하는 클래스를 뜻한다. 여러 클래스들이 서로 연관되어서 통신을 하게 될 경우

A,B 두개의 클래스라면 2개,A,B,C라면 6개..등등 클래스가 하나하나 늘어날때마다 제어해줘야하는

통신량은 어마어마하게 늘어나게 된다.


예를 들어 로그인 창을 하나 만든다고 할때, guest를 클릭하면 입력필드가 disable되고, login을 클릭하면 입력필드가 enable되면서 버튼이 생기고 이런 서로 연관된 동작을 가운데 중재자를 두어

통제하는 패턴 방식이다.


예제는 위에서 얘기한 로그인 애플릿 창이다.

등장 클래스..


1.Mediator - 카운셀러의 인터페이스를 정하는 인터페이스

2.Colleague - 멤버의 인터페이스를 정하는 인터페이스

3.ColleagueButton - 버튼을 나타내는 클래스

4.ColleagueTextField - 텍스트를 입력하는 클래스

5.ColleagueCheckBox - 첵크박스를 나타내는 클래스

6.LoginFrame - Mediator 인터페이스를 구현 , 로그인 다이얼로그를 나타내는 클래스


Mediator Interface

public interface Mediator {
    public abstract void createColleagues();
    public abstract void colleagueChanged(Colleague colleague);
}

createColleagues 메소드는 Mediator가 관리하는 멤버를 생성한다.

colleagueChanged는 상태가 변한 멤버들이 다음 자신의 행동을 결정하기 위해 호출하는

메소드이다. 인자는 자기 자신을 넘긴다.



Colleague Interface

public interface Colleague {
    public abstract void setMediator(Mediator mediator);
    public abstract void setColleagueEnabled(boolean enabled);
}

setMediator는 누구에게 자신의 다음 동작을 물어보면 될지 저장해두는 메소드이다.

setColleagueEnabled메소드는 카운셀러가 내리는 지시에 해당한다.



ColleagueButton클래스

import java.awt.Button;

public class ColleagueButton extends Button implements Colleague {
    private Mediator mediator;
    public ColleagueButton(String caption) {
        super(caption);
    }
    public void setMediator(Mediator mediator) {            // Mediator를 보관
        this.mediator = mediator;
    }
    public void setColleagueEnabled(boolean enabled) {      // Mediator가 유효/무효를 지시한다.
        setEnabled(enabled);
    }
}
setMediator를 이용해 카운셀러를 지정하고 있으며, 버튼은 스스로의 상태가 변한다기 보다는 체크박스나 텍스트필드의 변화에 의해 그 값이 바뀌기 때문에 카운셀러에게 물어보는 colleagueChanged메소드가 존재하지 않는다.


ColleagueTextField Class

import java.awt.TextField;
import java.awt.Color;
import java.awt.event.TextListener;
import java.awt.event.TextEvent;

public class ColleagueTextField extends TextField implements TextListener, Colleague {
    private Mediator mediator;
    public ColleagueTextField(String text, int columns) {   // 생성자
        super(text, columns);
    }
    public void setMediator(Mediator mediator) {            // Mediator를 보관
        this.mediator = mediator;
    }
    public void setColleagueEnabled(boolean enabled) {      // Mediator가 유효/무효를 지시한다.
        setEnabled(enabled);
        setBackground(enabled ? Color.white : Color.lightGray);
    }
    public void textValueChanged(TextEvent e) {             // 문자열이 변하면 Mediator에게 통지
        mediator.colleagueChanged(this);
    }
}

ColleagueCheckbox 클래스와 대동소이하며


public void textValueChanged(TextEvent e) {

   mediator.colleagueChanged(this);
    }
를 보면 텍스트필드에 어떤 이벤트가 발생할때마다 catch하여 그때마다 자기가 가지고 있는 카운셀러(mediator)에게 자신의 다음 상태를 물어보고 있다.



즉 동작의 프로세스를 보면 , LoginFrame에서는 각 멤버들을 생성시키고 그리고 그 멤버들의 setMediator메소드를 사용해 카운셀러를 지정한다. (여기서는 LoginFrame이 Mediator를 구현하여 colleagueChanged등의 메소드를 구현하고 있으므로 자기 자신을 카운셀러로 넘기게 되고, 이에 따라 각 멤버들인 자신에게 이벤트가 일어날때마다 LoginFrame이 가지고 있는 colleagueChanged메소드를 호출하게 된다.


 public void colleagueChanged(Colleague c) {
        if (c == checkGuest || c == checkLogin) {
            if (checkGuest.getState()) {             // Guest 모드
                textUser.setColleagueEnabled(false);
                textPass.setColleagueEnabled(false);
                buttonOk.setColleagueEnabled(true);
            } else {                                 // Login 모드
                textUser.setColleagueEnabled(true);
                userpassChanged();
            }
        } else if (c == textUser || c == textPass) {
            userpassChanged();
        } else {
            System.out.println("colleagueChanged:unknown colleague = " + c);
        }
    }

이런식으로 colleagueChanged메소드에서는 각 상태값을 체크하여 각각의 멤버에게 다음 상태값을 지정하여 준다. (setColleagueEnabled메소드)

신고
Posted by 용식


티스토리 툴바