Let's refactor (OOP style)

Theatrical players refactoring Kata in Object Oriented Programming

Start by establishing our plan

We can use the mikado method to do so.

Here is mine :

1) Extract the amount calculation

Create the interface

AmountCalculator.java
public interface AmountCalculator {
    Integer compute(Integer audience);
}

Extract the 2 implementations

public class ComedyCalculator implements AmountCalculator {
    @Override
    public Integer compute(Integer audience) {
        return 30_000 + 300 * audience + (audience > 20 ? 10_000 + 500 * (audience - 20) : 0);
    }
}

Create a factory to retrieve the implementation based on the type

AmountCalculators.java
public class AmountCalculators {
    public static Integer forTypeAndAudience(String type, Integer audience) {
        Map<String, AmountCalculator> types = new HashMap<>();
        types.put("tragedy", new TragedyCalculator());
        types.put("comedy", new ComedyCalculator());

        if (!types.containsKey(type)) {
            throw new Error("Unknown type: " + type);
        } else {
            return types.get(type).compute(audience);
        }
    }
}

2) Extract the credits calculation

private int volumeCredits(Invoice invoice, Map<String, Play> plays) {
    int credits = 0;
    for (Performance perf: invoice.performances) {
        Play play = playForPerformance(perf, plays);
        credits += Math.max(perf.audience - 30, 0);

        if ("comedy".equals(play.type)) {
            credits += perf.audience / 5;
        }
    }

    return credits;
}

3) Create a specific Printer implementation

Create types (simple POJOs) that represents a Statement

@Data
public class Statement {
    String customer;
    List<Line> lines = new ArrayList<>();
    Integer totalAmount;
    Integer volumeCredits;

    void addLine(Line line) {
        lines.add(line);
    }
}

Create a Printer interface

Printer.java
public interface Printer {
    String print(Line line);
    String print(Statement statement);
}

Extract the current print logic in a TextPrinter class

TextPrinter.java
public class TextPrinter implements Printer {
    @Override
    public String print(Line line) {
        return String.format("  %s: %s (%s seats)%n",
                line.getName(), Formatter.usd(line.getAmount()), line.getAudience());
    }

    @Override
    public String print(Statement statement) {
        return String.format("Statement for %s%n", statement.getCustomer())
                + statement.getLines().stream().map(this::print).collect(Collectors.joining(""))
                + String.format("Amount owed is %s%n", Formatter.usd(statement.getTotalAmount()))
                + String.format("You earned %s credits", statement.getVolumeCredits());
    }
}

4) Putting whole together

StatementPrinter.java
public class StatementPrinter {

    private Play playForPerformance(Performance perf, Map<String, Play> plays) {
        return plays.get(perf.playID);
    }

    private int volumeCredits(Invoice invoice, Map<String, Play> plays) {
        int credits = 0;
        for (Performance perf: invoice.performances) {
            Play play = playForPerformance(perf, plays);
            credits += Math.max(perf.audience - 30, 0);

            if ("comedy".equals(play.type)) {
                credits += perf.audience / 5;
            }
        }

        return credits;
    }

    private int totalAmount(Invoice invoice, Map<String, Play> plays) {
        int amount = 0;
        for (Performance perf: invoice.performances) {
            Play play = playForPerformance(perf, plays);
            amount += AmountCalculators.forTypeAndAudience(play.type, perf.audience);
        }
        return amount;
    }

    public String print(Invoice invoice, 
                        Map<String, Play> plays,
                        Printer printer) {
        Statement statement = new Statement();
        statement.setCustomer(invoice.customer);

        for (Performance perf : invoice.performances) {
            Play play = playForPerformance(perf, plays);
            int amount = AmountCalculators.forTypeAndAudience(play.type, perf.audience);
            statement.addLine(new Line(play.name, amount, perf.audience));
        }

        statement.setTotalAmount(totalAmount(invoice, plays));
        statement.setVolumeCredits(volumeCredits(invoice, plays));

        return printer.print(statement);
    }
}

Our code is now ready to be extended with the new HTLM printer.

Last updated