Чистый код. Создание, анализ и рефакторинг — страница 39 из 94

}

...

private void setIntArg(char argChar) throws ArgsException {

  currentArgument++;

  String parameter = null;

  try {

    parameter = args[currentArgument];

    intArgs.get(argChar).setInteger(Integer.parseInt(parameter));

  } catch (ArrayIndexOutOfBoundsException e) {

    valid = false;

    errorArgumentId = argChar;

    errorCode = ErrorCode.MISSING_INTEGER;

    throw new ArgsException();

  } catch (NumberFormatException e) {

    valid = false;

    errorArgumentId = argChar;

    errorParameter = parameter;

    errorCode = ErrorCode.INVALID_INTEGER;

    throw new ArgsException();

  }

}

...

public int getInt(char arg) {

  Args.ArgumentMarshaler am = intArgs.get(arg);

  return am == null ? 0 : am.getInteger();

}

...

private class ArgumentMarshaler {

  private boolean booleanValue = false;

  private String stringValue;

  private int integerValue;


  public void setBoolean(boolean value) {

    booleanValue = value;

  }


  public boolean getBoolean() {

    return booleanValue;

  }


  public void setString(String s) {

    stringValue = s;

  }


  public String getString() {

    return stringValue == null ? "" : stringValue;

  }


  public void setInteger(int i) {

    integerValue = i;

  }


  public int getInteger() {

    return integerValue;

  }

}

Переместив всю логику компоновки аргументов в ArgumentMarshaler, я занялся перемещением функциональности в производные классы. На первом этапе я должен был переместить функцию setBoolean в BooleanArgumentMarshaller и позаботиться о том, чтобы она правильно вызывалась. Для этого был создан абстрактный метод set.

private abstract class ArgumentMarshaler {

  protected boolean booleanValue = false;

  private String stringValue;

  private int integerValue;


  public void setBoolean(boolean value) {

    booleanValue = value;

  }

  public boolean getBoolean() {

    return booleanValue;

  }


  public void setString(String s) {

    stringValue = s;

  }


  public String getString() {

    return stringValue == null ? "" : stringValue;

  }


  public void setInteger(int i) {

    integerValue = i;

  }


  public int getInteger() {

    return integerValue;

  }


  public abstract void set(String s);

}

Затем метод set был реализован в BooleanArgumentMarshaller.

private class BooleanArgumentMarshaler extends ArgumentMarshaler {

  public void set(String s) {

    booleanValue = true;

  }

}

Наконец, вызов setBoolean был заменен вызовом set.

private void setBooleanArg(char argChar, boolean value) {

  booleanArgs.get(argChar).set("true");

}

Все тесты прошли успешно. Так как изменения привели к перемещению set в BooleanArgumentMarshaler, я удалил метод setBoolean из базового класса ArgumentMarshaler.

Обратите внимание: абстрактная функция set получает аргумент String, но реализация в классе BooleanArgumentMarshaller его не использует. Я добавил этот аргумент, потому что знал, что он будет использоваться классами StringArgumentMarshaller и IntegerArgumentMarshaller.

На следующем шаге я решил разместить метод get в BooleanArgumentMarshaler. Подобные размещения get всегда выглядят уродливо, потому что фактически возвращается тип Object, который в данном случае приходится преобразовывать в Boolean.

public boolean getBoolean(char arg) {

  Args.ArgumentMarshaler am = booleanArgs.get(arg);

  return am != null && (Boolean)am.get();

}

Просто для того, чтобы программа компилировалась, я добавил в ArgumentMarshaler функцию get.

private abstract class ArgumentMarshaler {

   ...

  public Object get() {

    return null;

  }

}

Программа компилировалась, а тесты, разумеется, не проходили. Чтобы тесты снова заработали, достаточно объявить метод get абстрактным и реализовать его в BooleanAgumentMarshaler.

private abstract class ArgumentMarshaler {

  protected boolean booleanValue = false;

    ...

    public abstract Object get();

  }


  private class BooleanArgumentMarshaler extends ArgumentMarshaler {

    public void set(String s) {

      booleanValue = true;

    }


    public Object get() {

      return booleanValue;

    }

  }

Итак, тесты снова проходят успешно. Теперь оба метода get и set размещаются в BooleanArgumentMarshaler! Это позволило мне удалить старую функцию getBoolean из ArgumentMarshaler, переместить защищенную переменную booleanValue в BooleanArgumentMarshaler и объявить ее приватной.

Аналогичные изменения были внесены для типа String. Я реализовал методы set и get, удалил ненужные функции и переместил переменные.

private void setStringArg(char argChar) throws ArgsException {

    currentArgument++;

    try {

      stringArgs.get(argChar).set(args[currentArgument]);

    } catch (ArrayIndexOutOfBoundsException e) {

      valid = false;

      errorArgumentId = argChar;

      errorCode = ErrorCode.MISSING_STRING;

      throw new ArgsException();

    }

  }

...

  public String getString(char arg) {

    Args.ArgumentMarshaler am = stringArgs.get(arg);

    return am == null ? "" : (String) am.get();

  }

...

  private abstract class ArgumentMarshaler {

    private int integerValue;


    public void setInteger(int i) {

      integerValue = i;

    }


    public int getInteger() {

      return integerValue;

    }


    public abstract void set(String s);


    public abstract Object get();

  }


  private class BooleanArgumentMarshaler extends ArgumentMarshaler {

    private boolean booleanValue = false;


    public void set(String s) {