Freitag, 30. August 2013

Quelltext zum Praktischen Beispiel 1.5

Wie angekündigt werde ich heute eine Version vorstellen, die ein weiteres Refactoring erfahren hat. Für die Anregung dazu möchte ich Rusi danken. Sein Codereview hat mich dazu veranlasst den Code noch einmal zu überarbeiten. In seinem überarbeiteten Code hat er die Klasse StartConditions in Operation aufgehen lassen. Auch bemerkte er die schwere Testbarkeit von OrderGenerator durch den new-Operator für die Klasse StartConditions. Dadurch konnten in der execute-Methode der Klasse Operation auch die Parameter entfallen. Ein weiterer Hinweis von Rusi war, dass für den ParameterParser eine Methode, statt drei, im Interface genügt. Mich haben diese Hinweise überzeugt und deshalb habe ich den Code überarbeitet. Nachzulesen ist der alte Code im Post Reengineering - Neu, strukturiert und auf lange Sicht schneller.

Letztendlich hat mich auch der Hinweis überzeugt, die ParameterParserFactory ist überflüssig. Für den ParameterParser wird es wahrscheinlich wirklich nur eine Implementierung geben. Ursprünglich wollte ich eine Verbindung zu meinem Factory-Artikel schaffen. Nachdem diese Idee für eine weitere Verwirrung gesorgt hatte, habe ich sie ganz fallen lassen.

Die Klasse StartConditions habe ich übrigens beibehalten. Ich habe sie aber vom OrderGenerator in die Klasse Operation verschoben.

Im nachfolgenden Quelltext wurde auf Kommentare verzichtet. In einem wirklichen Projekt plädiere ich dafür, an alle Klassen, Methoden und nicht gleich ersichtlichen Stellen kurze Kommentare zu schreiben.

Nachfolgend ist die Main des Bestellgenerators abgebildet. In der execute-Methode gibt es keine Factory mehr. Die Entscheidung, ob ich meinen selbstgeschriebenen Parser oder den Apache Commons CLI Parser verwende, fand ich am Ende doch recht konstruiert.

public class OrderGenerator {
    private final String[] args;

    public OrderGenerator(String[] args) {
        this.args = args;
    }

    protected void execute() {
        new CliParameterParser(args).parseOperation().execute();
    }

    public static void main(String[] args) {
        new OrderGenerator(args).execute();
    }
}


Der CliParameterParser ist mit dem Interface ParameterParser implementiert. Eventuelle andere Parser würden mit demselben Interface implementiert werden und könnten dann mit Hilfe der Dependency Injection injiziert werden.

public interface ParameterParser {
    Operation parseOperation();
}


In CliParameterParser muss die Methode parseOperation implementiert werden. In dieser ist ein allgemeiner Algorithmus zum parsen vorgeschrieben. Zuerst werden Initialisierungen durchgeführt, dann wird die Startbedingung (Berechnung oder Vorhersage) ermittelt, danach werden die Optionen geparst und zum Schluss wird noch geprüft, ob keine falschen Eingaben getätigt wurden. Die Parameter wurden bereits im Konstruktor übergeben.

public class CliParameterParser implements ParameterParser {
    private String[] args;

    private static CommandLineParser commandLineParser = 
              new BasicParser();
    private static CommandLine commandLine = null;
    private static Options options = new Options();
    private Operation operation;

    static {
        Option calculationOperation = new Option("cal", 
        "calculation", false, "Durchführung einer Berechnung.");
        Option forecastOperation = new Option("for", "forecast", 
        false, "Durchführung einer Vorhersage.");
        Option monthOption = new Option("m", "month", true, 
             "Wenn dieser Parameter gesetzt wird, muss zusätzlich 
              der Monat im Format 'yyyyMM' übergeben werden.");
        Option customerOption = new Option("c", "customer", true, 
             "Wenn dieser Parameter gesetzt wird, wird die Id 
              des Kunden übergeben.");

        OptionGroup startOptionGroup = new OptionGroup();
        startOptionGroup.addOption(calculationOperation);
        startOptionGroup.addOption(forecastOperation);

        startOptionGroup.setRequired(true);

        options.addOption(monthOption);
        options.addOption(customerOption);
        options.addOptionGroup(startOptionGroup);
    }

    public  CliParameterParser(String[] args) {
        this.args = args;
        try {
           commandLine = commandLineParser.parse(options, args, true);
        } catch (ParseException e) {
           operation = new Usage();
        }
    }

    @Override
    public Operation parseOperation() {
        Operation operation = checkStartCondition();
        parseOptions(operation);
        operation = sanityCheckParameter(operation);
        return operation;
    }

    private Operation checkStartCondition() {
        if (commandLine == null) {
            return new Usage();
        }
        setCalculation();
        setForecast();
        isNoCondition();
        return operation;
    }

    private void parseOptions(Operation operation) {
        if (commandLine == null) {
            return;
        }
        setMonth(operation);
        setCustomer(operation);
    }

    private void setCustomer(Operation operation) {
        if (commandLine.hasOption("customer")) {
            operation.getStartConditions().
             setCustomerId(commandLine.getOptionValue("customer"));
        }
    }

    private void setMonth(Operation operation) {
        if (commandLine.hasOption("month")){
            operation.getStartConditions().
             setMonth(commandLine.getOptionValue("month"));
        }
    }

    private Operation sanityCheckParameter(Operation operation) {
        List parameters = Arrays.asList(args);
        operation.getStartConditions();
        if (parameters.size() != StartConditions.getParameterCount()) {
            operation.getStartConditions();
            operation = new Usage();
        }
        return operation;
    }

    private void isNoCondition() {
        if (operation == null) {
            operation = new Usage();
        }
    }

    private void setForecast() {
        if (commandLine.hasOption("forecast") && operation == null) {
            operation = new Forecast();
            StartConditions.increaseProperlyPrameter();
        }
    }

    private void setCalculation() {
        if (commandLine.hasOption("calculation")) {
            operation = new Calculation();
            operation.getStartConditions();
            StartConditions.increaseProperlyPrameter();
        }
    }
}


Der Parser ermittelt die richtige Operation. Innerhalb der Operation gibt es die Klasse StartConditions. Darin werden die Werte zu den Optionen (Month, customer) gespeichert. Außerdem werden die ausgewerteten Parameter gezählt. So wird ermittelt, ob sinnlose Parameter übergeben wurden. Die spezifischen Operationen sind Ableitungen von Operation. Hier stelle ich nur die Operation Usage dar. Die anderen Operationen verfahren in der selben Weise und nutzen zum Start der Operation Polymorphie.

public abstract class Operation {
    private StartConditions startConditions = new StartConditions();

    public abstract void execute();

    public StartConditions getStartConditions() {
        return startConditions;
    }
}


public class StartConditions {
    private Date month;
    private Integer customerId;
    private static int parameterCount;

    public Date getMonth() {
        return month;
    }

    public void setMonth(String month) {
        SimpleDateFormat format = new SimpleDateFormat("yyyyMM");
        try {
            this.month = format.parse(month);
            parameterCount += 2;
        } catch (ParseException e) {
            // Muss nicht abgefangen werden
        }
    }

    public Integer getCustomerId() {
        return customerId;
    }

    public void setCustomerId(String customerId) {
        try {
            this.customerId = Integer.parseInt(customerId);
            parameterCount += 2;
        } catch (NumberFormatException e) {
            // Muss nicht abgefangen werden
        }
    }

    public static int getParameterCount() {
        return parameterCount;
    }

    public static void increaseProperlyPrameter() {
        StartConditions.parameterCount++;
    }
}


public class Usage extends Operation {
 @Override
 public void execute() {
  System.out.println("-java OrderGenerator 
    [-calculation|-forecast][-month ] [-customer ]");
 }
}


Die Enums ArgumentType und OperationType habe ich nicht umgesetzt.

Keine Kommentare:

Kommentar veröffentlichen