Turn a library written as loose dictionaries into the classes of its class diagram, so that every relationship and every multiplicity on the diagram is something you can point to in the code.
A library lends copies of books to members. The code in the editor does it with loose dictionaries and string names: it works, but nothing says what holds what, and nothing enforces the numbers. Rewrite it as the classes of this diagram, so each line on it is a line you can point to in your code:
%% title: The library loan
classDiagram
class Library {
+add_book(title, barcodes)
+join(member)
+lend(title, member, day) Loan
+give_back(loan)
}
class Book {
+title str
}
class BookCopy {
+barcode str
}
class Member {
+name str
}
class Loan {
+borrowed_day int
+fine(policy, returned_day) int
}
class FinePolicy {
<<interface>>
+fine(days_late) int
}
class PerDayFine
class LoanError
class NoCopyAvailable
Library "1" *-- "*" Book : catalogue
Book "1" *-- "1..*" BookCopy : copies
Library "1" *-- "*" Loan : loans
Library "*" o-- "*" Member : members
Loan "*" --> "1" BookCopy : of
Loan "*" --> "1" Member : by
Loan ..> FinePolicy : prices with
FinePolicy <|.. PerDayFine
LoanError <|-- NoCopyAvailableBuild this API (names are Python's; JavaScript and TypeScript use camelCase where they differ):
LoanError, and NoCopyAvailable which is a LoanError.FinePolicy (the interface: fine(days_late)) and PerDayFine(rupees_per_day) which implements it: rupees_per_day * days_late.BookCopy(barcode) with .barcode.Book(title, barcodes) creates a BookCopy for each barcode, in .copies. A book needs at least one copy (1..*): an empty list raises ValueError (JavaScript: an Error whose message contains at least one copy).Member(name) with .name.Loan(copy, member, borrowed_day) refers to the very objects it is given, in .copy and .member. Loan.fine(policy, returned_day) is policy.fine(days_late), where a loan lasts 14 days and days_late = max(0, returned_day - (borrowed_day + 14)). The policy is only a parameter: it works with any object that has fine(days_late), including ones the tests write.Library() with books (a dict from title to Book), members, and loans. add_book(title, barcodes) creates the Book. join(member) keeps the same Member object it is given (it never creates members). lend(title, member, day) returns a new Loan on the first copy of the title that has no active loan, and raises NoCopyAvailable("no free copy of <title>") when every copy is out. A member may have at most 2 active loans (0..2): a third raises LoanError("<name> already has 2 loans (limit 2)"), and this check comes before the search for a copy. give_back(loan) ends a loan, so its copy can be lent again; for a loan that is not active it raises LoanError.JavaScript and TypeScript: giveBack, borrowedDay, addBook, daysLate, PerDayFine(rupeesPerDay); the interface is the class FinePolicy (TypeScript: an abstract class), because the tests check instanceof.
The tests check the story of the lesson (2 copies, the third borrower refused, a fine of 10 for a loan returned on day 20), that each relationship is really there (copy is the object you passed, PerDayFine is a FinePolicy, NoCopyAvailable is a LoanError), both numbers, a fine policy the test writes, and that no loan is left as a loose dictionary in your code.
Write your code, then run 9 example tests and 3 hidden tests. Each run is limited to 8 seconds: an infinite loop gets stopped and the runtime restarts, your code stays.