A text user interface
With the logic tried out, the reading log needs a user interface. A text interface is a loop: read a command, carry it out, and do it again until the user is done. The interface gets a class of its own, ReadingApp, and its commands are words: add, read, left and quit. The app creates a ReadingLog and keeps it in a private attribute. Everything about books goes through it.
class ReadingLog:
def __init__(self):
self.__lengths = {}
self.__pages_read = {}
def add_book(self, title, length):
self.__lengths[title] = length
self.__pages_read[title] = 0
def has_book(self, title):
return title in self.__lengths
def read(self, title, pages):
self.__pages_read[title] += pages
def pages_left(self, title):
return self.__lengths[title] - self.__pages_read[title]
class ReadingApp:
def __init__(self):
self.__log = ReadingLog()
def show_commands(self):
print("commands: add, read, left, quit")
def add_book(self):
title = input("title: ")
length = int(input("pages in the book: "))
self.__log.add_book(title, length)
def read(self):
title = input("title: ")
if not self.__log.has_book(title):
print(f"{title} is not in the log")
return
pages = int(input("pages read: "))
self.__log.read(title, pages)
def left(self):
title = input("title: ")
if not self.__log.has_book(title):
print(f"{title} is not in the log")
return
print(f"{self.__log.pages_left(title)} pages left")
def run(self):
self.show_commands()
while True:
command = input("> ")
if command == "quit":
break
elif command == "add":
self.add_book()
elif command == "read":
self.read()
elif command == "left":
self.left()
else:
print(f"unknown command: {command}")
self.show_commands()
if __name__ == "__main__":
app = ReadingApp()
app.run()
add
Dune
412
read
Dune
50
read
Emma
left
Dune
help
quitcommands: add, read, left, quit
> add
title: Dune
pages in the book: 412
> read
title: Dune
pages read: 50
> read
title: Emma
Emma is not in the log
> left
title: Dune
362 pages left
> help
unknown command: help
commands: add, read, left, quit
> quitHere is who does what:
runis the main loop. It reads a command and passes the work on to the method for that command. Handing work over like this is called delegating.add_book,readandlefteach handle one command. They ask the user for what they need, call the logic, and show what it returns.- The interface never touches the dictionaries. When it needs to know something about the books, it asks
ReadingLog. - Checking what the user typed is the interface's job. Before it asks for pages,
readchecks withhas_bookthat the book exists, and tells the user if it doesn't.
Why not write everything inside the loop? Read run again: it works like a table of contents. It lists every command the app knows, and the details of each one are a method away. A new command is a new method and one more elif, and the commands that already work stay as they are.
ReadingLog and ReadingApp both have methods called add_book and read, and that's fine. The app's versions talk to the user, while the log's versions store the data. They never get mixed up, since each one is called on an object of its own class.