Start by establishing our plan
public interface AmountCalculator {
Integer compute(Integer audience);
}
public class ComedyCalculator implements AmountCalculator {
@Override
public Integer compute(Integer audience) {
return 30_000 + 300 * audience + (audience > 20 ? 10_000 + 500 * (audience - 20) : 0);
}
}
public class TragedyCalculator implements AmountCalculator {
@Override
public Integer compute(Integer audience) {
return 40_000 + (audience > 30 ? 1_000 * (audience - 30) : 0);
}
}
Create a factory to retrieve the implementation based on the type
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);
}
}
}
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
Create a Printer interface
public interface Printer {
String print(Line line);
String print(Statement statement);
}
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
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.