Servlet 에서, 또는 Switch - Case 등 많은 분기를 하는 방법에 대한 디자인 & 퍼포먼스 관점에서의 구경. ---- === 1. if-else === 대강 다음의 코드 스타일일것이다. {{{~cpp public class IfElse { public void printPerformance(String[] modeExecute) { long start; long end; start = System.currentTimeMillis(); for (int i = 0; i < ModeChoicePerformanceTest.LOOPING_COUNT; i++) { executeIfElse(modeExecute); } end = System.currentTimeMillis(); System.out.println("if - else elapsed time :" + (end - start) + "ms "); } private void executeIfElse(String[] modeExecute) { for (int i = 0; i < modeExecute.length; i++) { executeWithIfElse(modeExecute[i]); } } public void executeWithIfElse(String mode) { if (mode.equals("One")) { doOne(1); } else if (mode.equals("Two")) { doTwo(1); } else if (mode.equals("Three")) { doThree(1); } else if (mode.equals("Four")) { doFour(1); } else if (mode.equals("Five")) { doFive(1); } else { doDefault(1); } } public void doOne(int i) { i = 10; } public void doTwo(int i) { i = 10; } public void doThree(int i) { i = 10; } public void doFour(int i) { i = 10; } public void doFive(int i) { i = 10; } public void doDefault(int i) { i = 100; } } }}} 이 방법은 일단 속도상으론 가장 빠르다. 하지만, 한편으로 Seminar:WhySwitchStatementsAreBadSmell 에 걸리지 않을까? 근데.. 그에 대한 반론으로 들어오는것이 '이건 mode 분기이므로 앞에서의 Switch-Statement 에서의 예와는 다른 상황 아니냐. 어차피 분기 이후엔 그냥 해당 부분이 실행되고 끝이다.' 라고 말할것이다. 글쌔. 모르겠다. 한편으로 느껴지는 것으로는, switch 로 분기를 나눌 mode string 과 웹 parameter 와의 중복이 있을 것이라는 점이 보인다. 그리고 하나의 mode 가 늘어날때마다 해당 method 가 늘어나고, mode string 이 늘어나고, if-else 구문이 주욱 길어진다는 점이 있다. 지금은 메소드로 추출을 해놓은 상황이지만, 만일 저 부분이 메소드로 추출이 안되어있다면? 그건 단 한마디 밖에 할말이 없다. (단, 저 논문을 아는 사람에 한해서) GotoStatementConsideredHarmful. === 두번째 - Method reflection === {{{~cpp import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; public class MethodFullReflection { public void printPerformance(String[] modeExecute) throws InvocationTargetException, IllegalAccessException { long start; long end; start = System.currentTimeMillis(); for (int i = 0; i < ModeChoicePerformanceTest.LOOPING_COUNT; i++) { executeReflection(modeExecute); } end = System.currentTimeMillis(); System.out.println("elapsed time :" + (end - start) + "ms "); } private void executeReflection(String[] modeExecute) throws InvocationTargetException, IllegalAccessException { for (int i = 0; i < modeExecute.length; i++) { Method method = null; try { method = this.getClass().getMethod("do" + modeExecute[i], new Class[]{int.class}); method.invoke(this, new Object[]{new Integer(1)}); } catch (NoSuchMethodException e) { this.doDefault(1); } catch (SecurityException e) { e.printStackTrace(); //To change body of catch statement use Options | File Templates. } } } private void doDefault(int i) { i = 100; } public void doOne(int i) { i = 10; } public void doTwo(int i) { i = 10; } public void doThree(int i) { i = 10; } public void doFour(int i) { i = 10; } public void doFive(int i) { i = 10; } } }}} 장점 : MODE 가 추가될때마다 doXXX 식으로 이름을 정해주고 이를 실행하면 된다. 조건 분기 부분의 코드가 증가되지 않고, 해당 Mode 가 추가될때마다 메소드 하나만 추가해주면 된다. 단점 : 자바에서는 Method Reflection & Invoke 가 엄청 느리다.; 속도는 밑의꺼 참조. === 세번째. 위의 방법을 보완한 방법이다. 바로 일종의 Table Lookup. === {{{~cpp import java.util.HashMap; import java.util.Map; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; /** * User: Administrator Date: 2003. 7. 12. Time: 오전 12:48:38 */ public class MethodTableLookupReflection { public void printPerformance(String[] modeExecute) throws InvocationTargetException, IllegalAccessException { long start; long end; start = System.currentTimeMillis(); initReflectionMap(modeExecute); end = System.currentTimeMillis(); System.out.println("reflection with method initialize table elapsed time :" + (end - start) + "ms "); start = System.currentTimeMillis(); for (int i = 0; i < ModeChoicePerformanceTest.LOOPING_COUNT; i++) { executeReflectionWithMapping(modeExecute); } end = System.currentTimeMillis(); System.out.println("reflection with method elapsed time :" + (end - start) + "ms "); } private static Map methodMap; private void executeReflectionWithMapping(String[] modeExecute) throws InvocationTargetException, IllegalAccessException { for (int i = 0; i < modeExecute.length; i++) { Method method = (Method) methodMap.get(modeExecute[i]); if (method != null) method.invoke(this, new Object[]{new Integer(1)}); else doDefault(1); } } private void initReflectionMap(String[] methodNames) { methodMap = new HashMap(); for (int i = 0; i < methodNames.length; i++) { try { methodMap.put(methodNames[i], this.getClass().getMethod("do" + methodNames[i], new Class[]{int.class})); } catch (NoSuchMethodException e) { } catch (SecurityException e) { } } } private void doDefault(int i) { i = 100; } public void doOne(int i) { i = 10; } public void doTwo(int i) { i = 10; } public void doThree(int i) { i = 10; } public void doFour(int i) { i = 10; } public void doFive(int i) { i = 10; } } }}} 이 방법은 위의 방법과 같은 장점을 지니면서 퍼포먼스를 거의 10배가량 향상시킨다. === 네번째. Inner Class 에 대해 Command Pattern 의 사용. === {{{~cpp import java.util.Map; import java.util.HashMap; /** * User: Administrator Date: 2003. 7. 12. Time: 오전 12:57:7 */ public class InterfaceTableLookup { protected Map modeMap = new HashMap(); public void printPerformance(String[] modeExecute) { long start; long end; start = System.currentTimeMillis(); initModeMap(); end = System.currentTimeMillis(); System.out.println("interface table lookup init table elapsed time :" + (end - start) + "ms "); start = System.currentTimeMillis(); for (int i = 0; i < ModeChoicePerformanceTest.LOOPING_COUNT; i++) { executeInnerclassMapping(modeExecute); } end = System.currentTimeMillis(); System.out.println("interface table lookup elapsed time :" + (end - start) + "ms "); } private void executeInnerclassMapping(String[] modeExecute) { for (int i = 0; i < modeExecute.length; i++) { executeMode(modeExecute[i]); } } private void executeMode(String s) { IMode mode = (IMode) modeMap.get(s); if (mode == null) doDefault(1); else mode.execute(1); } public class ExOne implements IMode { public void execute(int i) { i = 10; } } public class ExTwo implements IMode { public void execute(int i) { i = 10; } } public class ExThree implements IMode { public void execute(int i) { i = 10; } } public class ExFour implements IMode { public void execute(int i) { i = 10; } } public class ExFive implements IMode { public void execute(int i) { i = 10; } } private void initModeMap() { modeMap.put("One", new ExOne()); modeMap.put("Two", new ExTwo()); modeMap.put("Three", new ExThree()); modeMap.put("Four", new ExFour()); modeMap.put("Five", new ExFive()); } } }}} 이 방법은 initModeMap 에서 매번 Mode에 대한 등록을 해줘야 한다. 퍼포먼스는 Method Reflection 보다 훨씬 빠르다. === 마지막 방법 - interface & reflection === 위의 방법에 initModeMap 을 reflection 으로 처리한 것이다. {{{~cpp import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; /** * User: Administrator Date: 2003. 7. 12. Time: 오전 1:2:16 */ public class InterfaceTableLookupReflection { public void printPerformance(String[] modeExecute) throws NoSuchMethodException, InstantiationException, ClassNotFoundException, InvocationTargetException, IllegalAccessException { long start; long end; start = System.currentTimeMillis(); initModeMapWithReflection(modeExecute); end = System.currentTimeMillis(); System.out.println("interface reflection & table lookup init able elapsed time :" + (end - start) + "ms "); start = System.currentTimeMillis(); for (int i = 0; i < ModeChoicePerformanceTest.LOOPING_COUNT; i++) { executeInnerclassMapping(modeExecute); } end = System.currentTimeMillis(); System.out.println("interface reflection & table lookup elapsed time :" + (end - start) + "ms "); } private void executeInnerclassMapping(String[] modeExecute) { for (int i = 0; i < modeExecute.length; i++) { executeMode(modeExecute[i]); } } private void executeMode(String s) { IMode mode = (IMode) modeMap.get(s); if (mode == null) doDefault(1); else mode.execute(1); } protected Map modeMap = new HashMap(); public final static String modeClassHeader = "Ex"; String expectedClassNameHeader = this.getClass().getName() + "$" + modeClassHeader; private void initModeMapWithReflection(String[] modeExecute) throws InstantiationException, InvocationTargetException, IllegalAccessException, NoSuchMethodException { Class[] consParamClasses = new Class[]{this.getClass()}; Object[] consParams = new Object[]{this}; Class[] inners = this.getClass().getClasses(); for (int i=0;i