제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.



자바 디자인 패턴 16 - Visitor

1. Visitor 패턴은..

복잡한 구조체 안을 돌아다니면서 어떤 일을 해야 할 경우가 있습니다. Visitor는 어떤 구조체에 대해 그 안을 돌아다니면서 어떤 일을 하는 것입니다. 이 때, 구조체 1개에 하는 일이 딱 1개라는 보장은 없습니다. 하나의 구조체에 대해 다양한 일들을 할 수 있습니다. 하고 싶은 일이 추가된다고 해서 구조체를 변경하는 것은 무리입니다. 이런 때는 Visitor를 추가하면 됩니다. 예제에서는 PC의 디렉토리-파일 구조에 대해 야동을 찾는 일을 하는 Visitor를 구현해보았습니다.

2. 예제

--------- Component, Composite, Leaf 등은 Composite 패턴 설명에 썼던 것을 거의 그대로 사용했습니다. 바뀐 부분은 색깔 처리했습니다. 처리한 부분만 보시면 됩니다.


package ch16_Visitor;
import java.util.ArrayList;
import java.util.List;

public abstract class Component implements Acceptor{
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    private String componentName;
    protected List<Component> children = new ArrayList<Component>();
    public Component(String componentName) {
        this.componentName = componentName;
    }
    public String getComponentName() {
        return componentName;
    }
    public abstract void add(Component c);
    public List<Component> getChildren(){
        return children;
    }
}

package ch16_Visitor;
public class Composite extends Component {
    public Composite(String componentName) {
        super(componentName);
    }
    @Override
    public void add(Component c) {
        children.add(c);
    }
}

package ch16_Visitor;
public class Leaf extends Component{
    public Leaf(String componentName) {
        super(componentName);
    }
    @Override
    public void add(Component c) {
        throw new UnsupportedOperationException();
    }
}
------------------ Visitor를 받아들일 수 있는 구조체 ----------------
package ch16_Visitor;

public interface Acceptor {
    void accept(Visitor visitor);
}
------------------ Acceptor를 방문하는 Visitor----------------
package ch16_Visitor;

public interface Visitor {
    void visit(Acceptor acceptor);
}
------------------ 야동 찾는 YadongFinder ----------------
package ch16_Visitor;
import java.util.ArrayList;
import java.util.List;
public class YadongFinder implements Visitor {
    private List<String> yadongList = new ArrayList<String>();
    private List<String> currentList = new ArrayList<String>();
    public void visit(Acceptor acceptor) {
        if (acceptor instanceof Composite) {
            Composite composite = (Composite) acceptor;
            currentList.add(composite.getComponentName());
            List<Component> children = composite.getChildren();
            for (Component component : children) {
                component.accept(this);
            }
            currentList.remove(currentList.size()-1);
        }else  if (acceptor instanceof Leaf) {
            Leaf leaf = (Leaf) acceptor;
            doSomething(leaf);
        }
    }
    protected void doSomething(Leaf leaf){
        if (isYadong(leaf)) {
                String fullPath = getFullPath(leaf);
                yadongList.add(fullPath);
            }
    }
    protected String getFullPath(Leaf leaf) {
        StringBuilder fullPath = new StringBuilder();
        for (String element : currentList) {
            fullPath.append(element).append("\\");
        }
        return fullPath.append(leaf.getComponentName()).toString();
    }
    private boolean isYadong(Leaf leaf) {
        return leaf.getComponentName().endsWith(".avi");
    }

    public List<String> getYadongList() {
        return yadongList;
    }
}
------------------ 테스트 클래스 ---------------- 
package ch16_Visitor;

public class Test {
    public static void main(String[] args) {
        Composite main = createComposite();
        YadongFinder visitor = new YadongFinder();
        visitor.visit(main);
        for (String string : visitor.getYadongList()) {
            System.out.println(string);
        }

    }

    private static Composite createComposite() {
        Composite main = new Composite("C:");
        Composite sub1 = new Composite("Program Files");
        Composite sub2 = new Composite("WINDOWS");
        Composite sub11 = new Composite("Pruna");
        Composite sub21 = new Composite("system32");
        Composite sub111= new Composite("Incoming");

        Leaf leaf1111 = new Leaf("강호동 닮은여자-짱이쁨.avi");
        Leaf leaf1112 = new Leaf("EBS야동특강.avi");
        Leaf leaf211 = new Leaf("야메떼-다이조부.avi");
        Leaf leaf212 = new Leaf("이건 야동아님.jpg");
       
        main.add(sub1);
        main.add(sub2);
        sub1.add(sub11);
        sub2.add(sub21);
        sub11.add(sub111);

        sub111.add(leaf1111);
        sub111.add(leaf1112);
        sub21.add(leaf211);
        sub21.add(leaf212);
        return main;
    }
}
---------------- 테스트 결과 -------------
C:\Program Files\Pruna\Incoming\강호동 닮은여자-짱이쁨.avi
C:\Program Files\Pruna\Incoming\EBS야동특강.avi
C:\WINDOWS\system32\야메떼-다이조부.avi

위의 예제에서 중요한 것은 Visitor의 visit(Acceptor) 와  Acceptor의 accept(Visitor)  입니다.

Visitor는 visit(Acceptor) 를 가지고 있고, Acceptor는 accept(Visitor) 를 가지고 있습니다. 둘의 차이가 헤깔립니다. 게다가 accept(Visitor) 를 구현해 놓은 것을 보면 아래와 같이 그냥 Visitor한테 자기 자신을 던져버리는 게 하는 일의 전붑니다.

public void accept(Visitor visitor) {
    visitor.visit(this);
}

이렇게 해 놓은 이유는 구조체를 돌아다닐 수 있게 하기 위한 것입니다. 실제로 구조체를 돌아다니는 일은 Visitor에서 담당하게 됩니다. 만약 이렇게 해놓지 않았다면, Visitor 안에서 구조체를 돌기 위해 재귀적인 호출을 해야만 복잡한 구조를 다 돌 수 있습니다. YadongFinder의 visit(Acceptor) 를 보면, 재귀적인 호출은 없습니다. 일반적으로 accept(Visitor)의 구현은 위와 같으며 달라질 일이 거의 없습니다.

Visitor의 visit(Acceptor)나 Acceptor의 accept(Visitor) 중 하나는 구조체를 도는 역할을 해야합니다. 구조체를 도는 역할은 Visitor의 visit(Accept)에 맡기는 것이 좋습니다. 구조체를 돌면서 하는 일 뿐만 아니라 "구조체를 도는 방법"도 다른 게 할 수도 있기 때문입니다. 위의 예제에서 YadongFinder는 아무래도 엄마가 짠 것 같습니다. 만약에 아들이 짰다면 구조체를 돌 때 "C:\Program Files\Pruna\Incoming\" 과 같은 비밀스러운 디렉토리는 슬쩍 뛰어넘는 로직을 짤 수 있었겠지요.

그러나 일반적으로 순화하는 로직은 거의 바뀌지 않습니다. 위의 예제에서는 YadongFinder를 상속 받아 doSomething(Leaf) 만 override 하면 뭔가 새로운 Visitor를 만들 수 있습니다.

3. 기타

visit(Acceptor) 안 쪽에서 instance of 로 어떤 클래스인지를 찾아냅니다. 이것은 visit(Composite) 와 visit(Leaf)로 분리시켰더라면 굳이 instance of를 쓸 필요가 없었을 것입니다. 하지만, 그렇게되면 Acceptor라는 인터페이스가 무의미해집니다.

Visitor와 Acceptor는 매우 밀접하게 묶여있습니다. 하지만 사실 Visitor의 구현체와 구조체 사이도 꽤 끈끈하게 묶여있습니다. YadongFinder 안에서 instance of로 체크한 것은 전부 구조체에서 정의된 클래스들(Composite, Leaf) 입니다.

테스트 코드의 YadongFinder를 선언하는 부분을 보면,
YadongFinder visitor = new YadongFinder();  와 같이 되어있습니다. 왜
Visitor visitor = new YadoingFinder(); 라고 인터페이스로 정의를 하지 않았을까요?

YadongFinder의 getYadongList() 부분이 포인트입니다. visitor는 구조체를 돌아다니면서직접 일을 할 수도 있습니다.( 예를들어, YadongFinder를 조금만 수정하면 YadongRemover 로 만들 수도 있습니다.) 하지만 돌아다니면서 뭔가를 수집하는 것과 같이 직접 구조체를 수정하지 않고 단지 정보만 수집하는 경우가 있는데, 그런 경우는 수집한 정보를 다시 뽑아서 사용할 수 있는 방법을 제공해야 합니다. Test 클래스에서 야동 리스트를 찍는 부분을 보시면 됩니다.

by 삼실청년 | 2009/04/08 00:29 | 컴터질~ | 트랙백 | 덧글(2)

제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.



자바 디자인 패턴 15 - Mediator

1. Mediator 패턴은..

비행기가 이착륙하다가 충돌하는 일은 좀체로 일어나지 않습니다. 비행기들끼리 서로 통신하지 않는데도 말이죠. 각각의 비행기는 관제탑하고만 통신을 하고, 관제탑이 각각의 비행기에게 착륙해도 된다 또는 안 된다 식으로 메시지를 보내줍니다. 비행기들끼리 서로서로 직접 통신을 한다면 통신할 경우의 수가 무진장 많아져서 혼란스럽게 됩니다. Mediator 패턴은 관제탑과 같이 통신을 집중시킴으로써 통신의 경로를 줄이고 단순화시키는 역할을 합니다.

2. 예제

------------------ 관제탑 역할을 하는 ControlTower (활주로 역할도 함) ----------------
package ch15_Mediator;

public class ControlTower {
    private volatile  boolean inUse;
   
    public synchronized boolean getPermission(){
        if (inUse) {
            return false;
        }else{
            inUse = true;
            return true;
        }
    }
   
    public void land(Airplane airplane) throws InterruptedException{
        int seq = airplane.getSeq();
        System.out.println(seq +"번 비행기 착륙 시작");
        Thread.sleep(50L);
        System.out.println(seq + "번 비행기 착륙 끝");
        inUse = false;
    }
}
------------------ 착륙허가를 받아야하는 Airplane ----------------
package ch15_Mediator;

public class Airplane extends Thread {
    private final ControlTower tower;
    private final int seq;

    public Airplane(ControlTower tower, int seq) {
        this.tower = tower;
        this.seq = seq;
    }

    public int getSeq() {
        return seq;
    }

    @Override
    public void run() {
        try {
            while (!tower.getPermission()) {
                // System.out.println(seq +"번 째 비행기 대기 중.");
                Thread.sleep(10L);
            }
            tower.land(this);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

------------------ 테스트 클래스 ----------------
package ch15_Mediator;

public class Test {
    public static void main(String[] args) {
        ControlTower tower = new ControlTower();
        Airplane[] airplanes = new Airplane[10];
        for (int i = 0; i < airplanes.length; i++) {
            airplanes[i] = new Airplane(tower, i);
        }
        for (Airplane airplane : airplanes) {
            airplane.start();
        }
    }
}

위 프로그램을 실행시키면, 비행기가 동시에 활주로를 사용하는 일은 발생하지 않습니다. 10대의 비행기는 모두 ControlTower를 가지고 있는데, 이는 동일한 ControlTower입니다. 즉, 비행기들끼리는 통신하지 않고 관제탑하고만 통신해서 충돌을 방지하게 되는 것입니다.
위의 예제에서 ControlTower는 모든 통신의 중계 역할을 합니다. 이를 Mediator(중계자)라 합니다. 그리고, Airplane은 Mediator와 통신을 하는 역할을 합니다. 이를 Colleague(사전적인 의미는 동료이지만, Mediator와 통신하는 객체들이라고 생각하시면 됩니다.) 라고 합니다.

위의 예제를 좀 더 확장해서 생각해 봅시다.

첫번째로 Airplane 은 ControlTower가 한 번 세팅이 되면 바꿀 방법이 없습니다. 위의 예제만으로는 비행기는 일회용이겠죠. 다른 공항에 착륙하고자할 때는 비행기가 통신할 관제탑이 바뀔 수도 있습니다. 그래서 보통은 Colleague 클래스는 setMediator 와 같은 메쏘드를 가지고 있습니다.

두번째로는 callback입니다. 예제에서는 비행기들이 관제탑에 "나 착륙해도 되?" 라고 계속 물어봅니다. "된다"라는 대답을 들을 때까지! 관제탑에서는 정신없습니다. (녹색부분의 주석을 풀어보면 얼마나 정신없는 지 바로 보입니다.)  
질문 방식을 바꾸면 어떨까요? 비행기가 "나 착륙할라고 하는데 활주로 비면 연락줘. 기둥기께" 라고 관제탑에 메시지를 보내고 관제탑은 그 비행기를 착륙 대기 리스트에 추가시켜 놓고, 리스트 앞에서 부터 각각의 비행기에게 "너 인제 착륙해도 된다"는 메시지를 보내면 됩니다. 이렇게 하면 통신 횟수를 확 줄일 수 있습니다.
callback은 명령을 내리고 임무를 완료하면 다시 연락하라는 겁니다. 짜장면 주문을 예로 들어보겠습니다. 짜장면집에 전화를 걸고, 주문을 합니다. 우리는 짜장면이 올 때까지 딴짓을 합니다. 짜장면이 배달오면 배달원이 초인종을 누릅니다. "주문" 이 명령이고, "배달"이 임무 완료입니다. "내"가 "짜장면집"에 명령을 하고 "짜장면집"은 그 명령을 수행합니다. "짜장면집"이 임무를 완수하면 "나"한테 "다시 연락"을 줍니다. 임무가 수행되는 동안 "나"는 "짜장면집"에서 짜장면을 잘 만들고 있는 지 신경을 쓰지 않습니다.

 세번째는 활주로가 1개 뿐이라는 겁니다. 활주로가 여러개라면 Runway 라는 활주로 클래스를 만들어야 합니다. 이 활주로는 당연히 ControlTower와 통신을 해야 합니다.  ControlTower가 어떤 비행기가 착륙 요청했다는 정보를 활주로에 알려주고, 비행기한테는 어떤 활주로에 착륙해라 라고 비행기와 활주로 사이에 관계를 맺어줍니다. 실제 착륙 작업에서는 더 이상 관제탑이 관여할 게 없습니다. 다만, 착륙이 완료 되면 활주로한테 이제 활주로가 다시 사용가능하다는 callback은 받아야겠죠.
이 경우 Runwary도 Colleague가 되어야 합니다. Colleague는 한 가지 특정타입(예제의 경우 Airplane)으로 국한될 필요는 없습니다.
Airplane의 getPermission은 getAvailableRunway로 바뀌고 리턴 타입역시 Runway로 바뀌어야겠습니다. 사용가능한 활주로가 없을 때는 null을 리턴한다거나 하면 되겠습니다.
그리고 land 메쏘드도 ControlTower가 아닌 Runway로 옮겨가야겠지요. 그리고 land 안에는 ControlTower에게 착륙이 끝났다는 정보를 전달할 수 있는 로직이 필요합니다.

3. 수 많은 Adapter가 필요한 경우

A,B,C 3개 회사가 합병을 했다 칩시다. 각각의 회원 정보를 다음과 같은 인터페이스로 정의해서 사용합니다.

interface ACompanyUser{     String getName();  }
interface BCompanyUser{     String getName();  }
interface CCompanyUser{     String getName();  }

이제 하나가 된 만큼 회원 정보를 서로 공유해야 하는데, 각 회사의 시스템은 예전 자기 회사의 인터페이스만을 받아들이도록 되어있습니다. 서로 캐스팅이 불가능한 객체를 캐스팅하고 싶을 때 Adapter 패턴을 씁니다. 다음과 같은 6개의 Adapter가 필요합니다.

AToBUser
AToCuser
BToAUser
BToCUser
CToAUser
CToBUser

아... 복잡해지기 시작합니다. 중계 객체를 하나 두면 어떨까요? 중계 객체를 M이라 합시다. 즉, A에서 B로 바꾸려면 위에서는 AToBUser 를 통해서 바꾸면 되었지만, 이제 AToM , MToB와 같이 두 단계를 거쳐서 만들면 됩니다. 단계가 늘어났지만 뭔가 좋은 게 있을 겁니다.

AToM
BToM
CToM
MToA
MToB
MToC

얼레? 똑같이 6갭니다. 그러나 갯수가 많아지면 얘기가 달라집니다. N개의 회사가 있다고 하면, 직접 변환을 할 경우 N*(N-1) 개의 인터페이스가 필요합니다. 그러나, 중계 객체를 만들면 2*N개만 있으면 됩니다. 4개의 회사의 경우 12개 - 8개 , 5개 회사는 20개-10개 와 같이 갯수가 많아 질수록 중계 객체를 두는 게 유리해집니다.

by 삼실청년 | 2009/02/16 23:24 | 컴터질~ | 트랙백 | 덧글(6)

제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.



java 실행 옵션 - 표준 옵션

참고 url : http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/java.html#options

-X 로 시작하는 옵션들은 표준 옵션이 아니며, 이에 대해서는 따로 다루겠습니다.
1.5 기준이며, 1.6에서는 두 세가지 옵션이 추가되었습니다. 그다지 중요하지 않아보이므로, 1.6에 추가된 옵션은 생략합니다.

-client
Java HotSpot Client 로 실행. client는 초기 구동 속도는 빠르지만 큰 작업을 할 때는 느립니다. window의 경우 기본값 
-server
Java HotSpot Server 로 실행. 초기 구동속도는 client에 비해 느리지만 큰 작업은 더 빠릅니다.
-agentlib:libname[=options]

다른 agent library를 사용. hprof 등 프로파일러를 사용할 수 있습니다. 자세한 것은 JVMTI Agent 에서 다루기로 하겠습니다.
-agentpath:pathname[=options]

-agentlib과 같지만 전체 경로로 호출합니다.
-classpath classpath
-cp classpath

다른 jar나 zip 등을 가져올 때 사용. 구분자는 :이며, 환경 변수로 CLASSPATH 가 설정되어있을 경우 이 옵션은 환경 변수 값을 덮어씁니다.
-Dproperty=value

시스템 속성값을 설정합니다. 

          System.getProperty(property)는 value 라는 값을 가지는 String을 리턴합니다.

-d32
-d64
32비트로 돌릴 것인지 64비트로 돌릴 것인지를 선택합니다. 지금은(1.5) -server 모드에서는 64비트만 지원한답니다.

-enableassertions[:<package name>"..." | :<class name> ]

-ea[:<package name>"..." | :<class name> ]
assertion을 활성화시킵니다. 기본적으로는 활성화되어 있지 않습니다. 인자가 없을 경우는 모든 assertion을 활성화시키고, ...을 포함한 인자가 있을 경우는 그 패키지와 하위 패키지의 assertion을 활성화 시킵니다. 또, ...이 없이 패키지 이름만 있을 경우는 하위 패키지를 제외한 그 패키지만 활성화 시킵니다. 그냥 ...만 있을 경우는 기본 패키지만 활성화시킵니다.
           ex> java -ea:com.wombat.fruitbat... <Main Class>

           system에 관련된 assertion은 제외되며, 이를 포하하고자 할 경우에는 아래에 있는 -enablesystemassertions 를 참고하면 됩니다.

-disableassertions[:<package name>"..." | :<class name> ]
-da[:<package name>"..." | :<class name> ]
assertion을 비활성화하며, 사용법은 -ea 와 유사합니다.
-enablesystemassertions
-esa

모든 시스템 클래스의 assertion을 활성화합니다.

-disablesystemassertions
-dsa
모든 시스템 클래스의 assertion을 비활성화합니다.

-jar

클래스가 아니라 jar 파일을 실행시킵니다. jar 안에 menifest 파일을 잘 만들어놔야 됩니다.

-javaagent:jarpath[=options]
자바 프로그래밍 랭귀지 에이전트를 로드합니다. (자세한 건 나중에..)  java.lang.instrument 를 보면 잘 나와있습니다.
-verbose
-verbose:class
로드된 각각의 클래스 정보를 보여줍니다.
-verbose:gc
GC 실행정보를 보여줍니다.

-verbose:jni

native 메쏘드와 JNI 에 대한 정보를 보여줍니다.

-version

버젼정보를 보여주고 프로그램을 종료합니다.

-showversion

버젼정보를 보여주고 프로그램을 계속 실행합니다.
-?
-help

help 정보를 출력합니다.

-X
비표준 옵션들을 보여주고 프로그램을 종료합니다.

by 삼실청년 | 2009/02/08 00:47 | 컴터질~ | 트랙백 | 덧글(0)

제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.



java enum - 보충

본 내용은 Paleys.com 에 있는 Joshua bloch의 Effective java reload 동영상을 바탕으로 쓴 글입니다.

eunm에 대한 기본 지식이 없다면 이전에 정리해 놓은 글들을 먼저 보시고 보시는 게 좋을 듯합니다.

1. ordinal

enum에는 ordinal() 메쏘드가 있는데, 이 것은 각 element의 순서대로 번호가 생깁니다. 따라서 몇 번째 놈인지를 알아낼 수 있는데, 일반적으로는 사용하지 않는 것이 좋다고 합니다. ordinal의 기본 용도는 jdk 안에 있는 EnumSet이나 EnumMap 등의 성능을 위해 있는 것이지 사용자를 위해 제공하는 것이 아니라고 합니다. 만약 순서가 필요할 경우 final int 로 저장을 하라고 합니다.
ex>
--- 틀린 예
public enum Day{ SUN, MON,....}
--- 옳은 예
public enum Day{ 
    SUN(1), MON(2), ... ;
    private final int seq;
    public Day(int seq){this.seq = seq}
    public int getSequence(){ return seq; }
}

2. enum을 이용한 singleton

singleton 패턴이 많이 쓰이기는 하지만 몇 가지 보안상 취약한 부분들이 있답니다. (특히 serialize 관련된 부분, 개인적으로 java의 Serialize는 저주라고 생각합니다!) 또, 만들기도 구찮은 부분이 있습니다. (private constructor 등)
ex>
--- class를 이용한 singleton -- Serializable의 저주가 함께하는 클래스!
public class Singleton implements Serializable{
    public static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    .. 기타 멤버 변수와 각종 메쏘드들.
}
--- enum을 이용한 singleton
public enum Singleton{
    INSTANCE;
    .. 기타 멤버 변수와 각종 메쏘드들.
}

어차피 singleton은 상속이 안 되니까, enum으로 해도 상관없고, 코드도 깔끔해졌습니다. 당근 serialize에 관련된 문제도 해결되었구요.

3. enum의 상속

enum은 기본적으로 상속이 안 됩니다. 상속이 되면 여러가지 문제가 발생할 수 있어서 상속을 없앴다고 합니다. 그러나 interface를 이용할 수는 있습니다.
ex>
public interface Direction{
    int getAngle();
}

public enum DefaultDirection implements Direction{
    동(90), 서(270), 남(180), 북(0);
    private final int angle;
    DefaultDirection (int angle){
        this.angle=angle;
    }
    public int getAngle(){
        return angle;
    }
}

public enum ComplexDirection implements Direction{
    동북(45), 서북(315) , 동남 (135) , 서남(225);
    private final int angle;
    ComplexDirection (int angle){
        this.angle=angle;
    }
    public int getAngle(){
        return angle;
    }
}

by 삼실청년 | 2009/02/04 11:36 | 컴터질~ | 트랙백 | 덧글(0)

제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.



EnumSet

본 내용은 Paleys.com 에 있는 Joshua bloch의 Effective java reload 동영상을 바탕으로 쓴 글입니다.

EnumSet은 bit 필드를 대신하기 위해서 나온 것입니다.

다음과 같은 코드를 생각해봅시다.

public class Text{
    public static final int STYLE_BOLD = 1;
    public static final int STYLE_ITALIC = 2;
    public static final int STYLE_UNDERLINE = 4;
    public static final int STYLE_STRIKETHROUGH = 8;

    // 인자는 STYLE_ 의 조합으로 이루어진 데이터.
    public void applyStyles(int styles){ ... }
}

이 것을 사용하는 클라이언트 코드는

text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

와 같이 됩니다.

일반적인 list나 set에 비해 bit 연산으로 처리하면 좋은 점은 속도가 월등히 빠르다는 것입니다.

그러나 여기에는 몇가지 문제 점들이 있습니다.
  1. type이 보장되지 않습니다. BOLD는 1이지만, 1이 반드시 BOLD의 의미로 쓰인다는 보장이 없습니다.
  2. 1에 연관되는 문제로 STYLE_과 같은 식의 prefix가 없으면 보기가 졸라 헤깔립니다. 또한 STYLE에 관련되지 않은 다른 종류의 상수들이 왕창 있을 경우 더욱 복잡해집니다. 예를 들어 FONT_SMALL = 1; 와 같은 것이 있다면, STYLE_BOLD와 값은 같지만 의미하는 바가 전혀 다르며, 전적으로 prefix에 의존해서 구분하는 수 밖에 없게 됩니다.
  3. 안전하지 못합니다. java의 constant는 값을 컴파일 시점에 값을 복사합니다. 따라서 api에서 코드를 바꿀 경우 클라이언트 코드가 정상적으로 작동하지 않습니다.
    ex>
    public class Constants {
        public static final String name = "홍길동";
    }
    public class AnotherClass{
         String a =  Constants.name;
    }

    이것은 컴파일 시점에 
    String a= "홍길동";
    으로 바뀝니다. 따라서 Constants 클래스의 name값을 바꾸고 Constants만 다시 컴파일할 경우 AnotherClass의 a 는 여전히 "홍길동"입니다.
  4. 값을 찍었을 때 무슨 뜻인지 직관적이지 않습니다. 예를들어, 3은 BOLD와 ITALIC의 의미가 됩니다.
  5. 값을 iteration 을 돌리기가 쉽지 않습니다. 비트 단위로 하나씩 자리수를 변경시켜가며 값이 있는지 없는 지 체크해야 합니다.
  6. 갯수가 너무 많을 경우 커버할 수 없습니다. int가 아니라 long으로 하더라도 그 범위를 넘어가는 것은 표현할 수 없습니다.

이에 대한 해결책으로 EnumSet 이 있습니다. 위의 예제를 enum과 EnumSet을 이용하여 다시 쓰면 아래와 같이 됩니다.

public class Text{
    public enum Style{
        BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
    }
    //어떤 종류의 Set의 구현체이건 다 들어갈 수 있지만, EnumSet이 젤 좋다.
    public void applyStyles(Set<Style> styles){ ... }
}

클라이언트 코드
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));

이렇게 하면 위에서 열거한 모든 문제들이 해결됩니다.

  1. 타입이 안전해 졌습니다.
  2. enum 자체가 name space의 역할을 합니다.
  3. 상수와는 달리 동적으로 클라이언트 코드에 링크가 되기 때문에, api를 바꾸어도 클라이언트에 지장을 주지 않습니다.
  4. 이름이 주어지기 때문에 읽기가 쉽습니다.
  5. for(Style style : styles) 와 같이 iteration이 쉬워집니다.
  6. 갯수 제한도 없습니다.

위에서 열거한 점들은 bit 연산을 그냥 Set으로 바꿨을 때도 다 해결되는 문제들입니다. 그러면 EnumSet이 더 좋은 이유를 알아봅시다.

EnumSet은 contains 등의 메쏘드에서 Enum.ordinal() 등을 사용하여 비트 연산과 속도 차이가 거의 나지 않습니다. 따라서 HashSet보다 더 빠릅니다.
EnumSet의 static 메쏘드인 of를 사용하게 되면, 그 enum의 크기(element의 갯수)에 따라 JumboEnumSet과 RegularEnumSet (둘다 abstract class인 EnumSet을 상속받음) 중에 선택되어 객체를 생성합니다. (EnumSet.of() 는 factory method란 뜻이죠.)

클라이언트 코드를 HashSet으로 바꾸게 되면,
Set<Style> styles = new HashSet<Style>();
styles.add(Style.BOLD);
styles.add(Style.ITALIC);
text.applyStyles(styles);
와 같이 번거롭게 됩니다. 물론 좀 줄이면

text.applyStyles( new HashSet<Style>(){ { add(Style.BOLD); add(Style.ITALIC);}});

과 같이 되긴 합니다만, EnumSet.of 를 쓰는 코드에 비하면 역시나 번거롭기 짝이 없습니다.

by 삼실청년 | 2009/02/03 22:47 | 컴터질~ | 트랙백 | 덧글(8)

◀ 이전 페이지          다음 페이지 ▶