이 글에서 분류한 기준은 책의 내용을 바탕으로 주관적인 견해로 재정리해본 것입니다.
모듈을 개선하고 정리하는 내용이어서, 모든 내용을 정리하지 않고 필요한 부분만 체크하여 정리할 것이다.
main
함수로 넘어오는 명령행 인수의 구문을 분석하는 모듈이다.-l
: 부울 인수-p
: 정수 인수-d
: 문자열 인수public static void main(String[] args) {
try {
// (1) Args 생성자에 인수 문자열과 형식 문자열을 넘겨 Args 인스턴스를 생성한다.
Args arg = new Args("l,p#,d*", args);
// (2) Args 인스턴스에 인수 값을 질의한다.
boolean logging = arg.getBoolean('l');
int port = arg.getInt('p');
String directory = arg.getString('d');
// (3) 애플리케이션 실행 코드 (중요하지 않다.)
executeApplication(logging, port, directory);
} catch (ArgsException e) {
// 만약 (1)에서 형식 문자열이나 명령행 인수 자체에 문제가 있다면
// ArgsException이 발생한다.
System.out.println("Argument error: %s\n", e.errorMessage());
}
}
리팩터링을 하면서 추가되는 코드이다.
public static XXX getValue(ArgumentMarshaler am)
메소드가 추가되어 있다.public interface ArgumentMarshaler {
void set(Iterator<String> currentArgument) throws ArgsException;
}
public class BooleanArgumentMarshaler implements ArgumentMarshaler {
private boolean booleanValue = false;
// set 함수 구현
@Override
public void set(Iterator<String> currentArgument) throws ArgsException {
booleanValue = true;
}
// boolean 인수 반환
public static boolean getValue(ArgumentMarshaler am) {
if (am != null && am instanceof BooleanArgumentMarshaler)
return ((BooleanArgumentMarshaler) am).booleanValue;
else
return false;
}
}
public class StringArgumentMarshaler implements ArgumentMarshaler {
private String stringValue = "";
// set 함수 구현
@Override
public void set(Iterator<String> currentArgument) throws ArgsException {
try {
stringValue = currentArgument.next();
} catch (NoSuchElementException e) {
throw new ArgsException(MISSING_STRING);
}
}
// String 인수 반환
public static String getValue(ArgumentMarshaler am) {
if (am != null && am instanceof StringArgumentMarshaler)
return ((StringArgumentMarshaler) am).stringValue;
else
return "";
}
}
본격적으로 개선하기 전에, 최종적인 모듈이 어떻게 동작하는 지를 알고 가자.
코드를 분석한 뒤, 필요하다 싶은 곳만 남기고 주석을 달아봤다.
Args
클래스 하나로 구현되어 있다.ParseException
를 던진다.public class Args {
// 길다란 변수 목록
private enum ErrorCode {
// 오류 코드
}
public Args(String schema, String[] args) throws ParseException {
this.schema = schema;
this.args = args;
valid = parse();
}
private boolean parse() throws ParseException {
if (schema.length == 0 && args.length == 0)
return true;
parseSchema();
try {
parseArguments();
} catch (ArgsException e) {
}
return valid;
}
private boolean parseSchema() throws ParseException {
for (String element : schema.split(",")) {
if (element.length() > 0) {
String trimmedElement = element.trim();
parseSchemaElement(trimmedElement);
}
}
return true;
}
private void parseSchemaElement(String element) throws ParseException {
// 스키마(형식 문자열)의 타입 별로 구분한 뒤,
// parsXXXSchemaElement(char elementId) 메소드를 호출한다.
}
// parsXXXSchemaElement(char elementId) : boolean 타입
private void parseBooleanSchemaElement(char elementId) {
booleanArgs.put(elementId, false);
}
// parsXXXSchemaElement(char elementId) : int 타입
private void parseIntegerSchemaElement(char elementId) {
intArgs.put(elementId, false);
}
// ...
// 명령행 인자를 배열에 저장하는 set함수와 가져오는 get함수
// 에러 메시지를 출력하는 함수 등
}
새 인수 유형을 추가하려면 주요 지점 세 곳에 코드를 추가해야 했다.
ArgumentMarshaler
개념 탄생ArumentMarshaler
개념에 가까워졌다.ArgumentMarshaler
클래스에 넣고 나서, 추후 파생 클래스를 만들어 코드를 분리할 예정으로 작업한다.ArgumentMarshaler
로 옮긴 후, 파생 클래스를 만들어 기능을 분산한다.ArgumentMarshaler
클래스를 abstract class
로 변경해서 파생 클래스를 만들었다.setArgument
메서드에서setArgument
는 파생 클래스의 메서드를 호출하게 수정 (책임 전가)setArgument
메서드에서 인수 유형을 일일이 확인하던 코드를 제거ArgumentMarshaler
를 인터페이스로 변경한다.DoubleArgumentMarshaler
클래스를 추가ParseException
는 Args 클래스에 속하지 않는다.ArgsException
클래스를 만든 후 독자 모듈로 옮긴다.코드는 언제나 최대한 깔끔하고 단순하게 정리하자