PythiloLesson 14 of 15 · progress kept in this browser

Open for extension

An if/elif chain on a kind, such as if fmt == "csv": ... elif fmt == "json": ..., has to be edited for every new kind, and the same chain tends to appear in several places. The open/closed principle says code should be open to extension but closed to modification: adding a case should mean adding code, not editing code that works.

Put the varying behaviour behind one interface. Classes that share a method name do it, and so does a registry: a dict from name to function, filled by a decorator.

EXPORTERS = {}

def exporter(name):
    def register(func):
        EXPORTERS[name] = func
        return func
    return register

@exporter("csv")
def to_csv(rows):
    lines = [",".join(row) for row in rows]
    return "\n".join(lines)

rows = [["a", "b"], ["c", "d"]]
print(EXPORTERS["csv"](rows))

A new format is now a new decorated function, possibly in another module, and nothing else changes.

Your task

Replace the chain with a registry: a dict DISCOUNTS, a decorator discount_rule(name) that stores a function in it and returns the function unchanged, one function per kind, and apply_discount(kind, total) that looks the rule up, raising ValueError for an unknown kind. Then add "fifth_off", which takes 20% off.