Putting it together: a sales report
A capstone that uses the whole course once: an interface that promises a total, records as data classes, one transformation per question, and a report printed at the end.
The shape of a small program
Twelve chapters of pieces, arranged into one file. The records are a data class with a constructor, so each sale carries its own three values. The interface Priced promises one function — total — and the data class keeps that promise with a single line. Anything that can give a total can stand in for a sale, and the rest of the file no longer needs to know which kind of thing it was handed.
An interface is a promise about behaviour, not a place to store values. It is how a program says "anything shaped like this will do", and it is the reason a later edit can introduce a second kind of record without touching the report. The promise is checked by the compiler: a class that says it implements Priced must supply total, or the file does not compile.
One question, one transformation
Each line of the report asks one question of the list. Ordering by size is sortedByDescending; the grand total is sumOf; how many sales were large is count; the smallest is minOf. Every one of them takes a lambda, so the question is written in the place where the answer is used.
The loop is only there to print. Keeping the questions apart from the printing is what makes the file readable: adding a question tomorrow means adding one line above the report, and changing the wording of a line means changing one line inside it. A report that computes inside its print loop mixes the two jobs, and every later change has to touch both.
Where to go next, and one habit to keep
Amounts are counted here in whole cents, which is how money arithmetic stays exact. Turning 6850 cents into a printed amount is a line of text work from chapter three: divide by a hundred for the units, and pad the remainder with a zero when it is small. Do that at the moment of printing, never before. The arithmetic stays in Ints; only the final line sees a decimal point.
From here the language opens out: coroutines for work that waits, the collections reference for the calls this course did not have room for, and Android or a server framework as the next room. The habit worth carrying is the one this file is built on — records as data classes, questions as transformations, and formatting only at the end.
A second implementation costs one line
The interface earns its place when a second kind of row appears. A refund is a record with a negative total; a discount line is a record whose total is a subtraction. Both can be put in the same list because both can answer total(). The report does not ask what kind of row it has, so the new type does not require a new branch in the printing loop.
That is the difference between a program that grows by adding types and one that grows by editing a chain of conditions. The first keeps the old code still; the second makes every new case a change to code that already worked. An interface is worth its extra line when you can name a second implementation that is plausible, not merely possible.
The promise, the record, and the report
interface Priced {
fun total(): Int
}
data class Sale(
val item: String, val qty: Int, val price: Int
) : Priced {
override fun total() = qty * price
}
fun main() {
val sales = listOf(
Sale("Cable", 3, 450), Sale("Hub", 1, 3200),
Sale("Mouse", 2, 400), Sale("Stand", 1, 1500)
)
for (sale in sales.sortedByDescending { it.total() }) {
println(sale.item + " " + sale.total())
}
println("Total cents: " + sales.sumOf { it.total() })
println("Over 1000: " + sales.count { it.total() > 1000 })
println("Cheapest: " + sales.minOf { it.total() })
}The report, in one file
Run it and read the three sections of the report. Then add a fifth sale of your own, print how many sales there are, and add a line that shows the average total in whole cents.
The interface is the seam the report depends on
Priced names one function and says nothing about storage. Sale promises to provide it, and override is the keyword that says this body is the one the interface asked for. The report can then work with anything that keeps the promise: a sale, a refund, a discount line. That is polymorphism in one sentence — the caller depends on the behaviour, and the concrete type stays at the edge of the program.
Money stays in whole cents while the report computes, because every addition is exact. The conversion to text happens once, at the printing line, where the remainder is padded with a leading zero. If you format first and add afterwards, you are adding text, and the cents that were rounded away are gone for good.
Three capstone mistakes are worth a final look. A function that implements an interface member needs override; without it the compiler refuses the class. Integer division throws away the remainder, so compute an average only after summing, and decide whether the remainder should be shown. And formatting money before the total loses the cents that the total needs — keep the numbers exact until the last line.
Try these next
- Add a second implementation of the interface, such as a refund with a negative total, and put it in the same report.
- Add a line that prints the most expensive item by name, using
maxByOrNull. - Add a line that prints the average remainder in cents, and decide whether the report should round it up.
An expense report with exact cents
Run it and check the total against the printed lines. Then add a fifth expense, change the average line to show a remainder, and move the money formatting into a second function that takes a label.
Change one sale and only the report changes: the record holds the values, the transformations ask the questions, and the printing stays at the bottom of the file. That separation is what makes a program of any size possible to read.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does the interface in this file promise?
