Programmering grundkurs Föreläsning 10 Jody Foo, jody.foo@liu.se
Föreläsningsöversikt Laboration 6 Abstraktion och OOP Klassdiagram med UML (Unified Modelling Language) Egna klasser som innehåller andra egna klasser Nedbrytning av problem genom fördelning av ansvar, "delegera vidare uppgiften" Olika implementationsmönster inom OOP Interaktiva textgränssnitt Instans som huvudprogram
Laboration 6 OOP: nya mönster uppdelning av problem klasser med olika roller Interaktiva textgränssnitt
Abstraktion och OOP Göm undan logistik Delegera uppgift det till rätt klass - alla behöver inte se exakt hur allt fungerar Olika nivåer av problemlösning - olika abstraktionsnivåer, behöver vi prata om ettor och nollor, siffror, tecken, listor, tågbiljetter, reseplanering, sammanfattning av resan osv OBS! Med "Delegera uppgift" menar ej här OOP designmönster "Delegation Pattern" (som ligger utanför kursens omfång)
Klassdiagram i UML ett slags pseudokod för OOP
Klassdiagram i UML Unified Modelling Language Klassnamn namn : attributtyp namn : attributtyp = defaultvärde operation() operation() : returdatatyp operation(parameterlista) operation(parameterlista) : returdatatyp
Exempel DataRow trial : str image_file : str time : int correct_answer : str subjects_answer : str degrees_rotated : int get_error_type() : int
UML, klassdiagram: relationer mellan objekt Objekt kan ha någon form av relation till andra objekt association: allmän relation. kardinalitet (antal) och roll (i praktiken variabelnamn) roll: lästa_böcker squares contacts kardinalitet 0..1: noll eller en 0..*: noll eller flera 1..*: en eller fler 3: exakt tre 0..3: noll till tre 5..9: fem till nio
UML: Relationer En dubbelriktad association kan ritas utan pilar.
Exempel LayoutTester layout_func : callable squares_frame_height : int squares_frame_width : int size_options : list ui_xpadding : int ui_ypadding : int squares 0..* tkinter.tklabel winfo_width() : float place(x : float, y : float) init_ui() init_control_panel() create_size_panel() create_num_squares_panel() create_start_pos_panel() create_run_quit_panel() create_squares() clear_squares() run_layout()
Exempel användardefinierad klass som innehåller instanser av andra användardefinierade klasser
Modell för en väska En väska kan öppnas och stängas. Vi kan stoppa in saker i en väska, men inte hur många saker som helst. En väska är ett förvaringsutrymme som har ett blixtlås. Om blixtlåset är öppet kan vi stoppa in saker. Om blixtlåset är trasigt kan vi inte stoppa in saker. Varje gång vi öppnar eller stänger väskan finns det en viss sannolikhet att blixtlåset går sönder.
bagscript.py from zcb import * # Bag är en klass definierad i filen zcb.py # Vad ser koden ut att göra? bag = Bag() bag.open() bag.add("candy") bag.close() bag.open() bag.add("egg") bag.close() bag.add("flower") print(bag)
Klassen Bag Bag color : str = "green" zipper : Zipper container : Container open() close() add(item : str)
zcb.py - klassen Bag class Bag(object): def init (self, color="green"): self.color = color self.zipper = Zipper() self.container = Container() def open(self): self.zipper.open() def close(self): self.zipper.close() Bag color : str = "green" zipper : Zipper container : Container open() close() add(item : str) def add(self, item): item = item.upper() print("trying to add '{}' to bag..".format(item)) if self.zipper.is_open(): self.container.add(item) else: print("open zipper first.") def str (self): return "A {} bag. {} {}".format(self.color, self.zipper, self.container)
Klassen Zipper Zipper state : str = "open chance_to_break : float = 0.2 is_open() : boolean is_broken() : boolean open() close() try_to_break()
zcb.py - klassen Zipper class Zipper(object): def init (self): self.state = "open" self.chance_to_break = 0.2 def is_open(self): return self.state == "open" def is_broken(self): return self.state == "broken" def open(self): print("trying to open zipper...") self.try_to_break() if not self.is_broken(): self.state = "open" print(self) else: print("cannot open zipper. {}".format(self)) Zipper state : str = "open chance_to_break : float = 0.2 is_open() : boolean is_broken() : boolean open() close() try_to_break() def close(self): print("trying to close zipper...") self.try_to_break() if not self.is_broken(): self.state = "closed" print(self) else: print("cannot close zipper. {}".format(self)) def try_to_break(self): if random.random() <= self.chance_to_break: self.state = "broken" print("the zipper broke!") def str (self): return "The zipper is {}.".format(self.state)
Klassen Container Container items : list = [] capacity : int = 5 add(item : str)
zcb.py - klassen Container class Container(object): def init (self, capacity=5): self.items = [] self.capacity = capacity Container items : list = [] capacity : int = 5 add(item : str) def add(self, item): if len(self.items) < self.capacity: self.items.append(item) print("{} added.".format(item)) else: print("{} not added. Container full.".format(item)) def str (self): if not self.items: items_str = "None" else: items_str = ", ".join(self.items) description = "The container is {} percent full. Items: {}." return description.format(len(self.items)/self.capacity * 100, items_str)
Klassdiagram med UML Enkelriktade associationer (vi använder oss endast av associationsrelationer i denna kurs) Bag känner till en instans av klassen Zipper. Zipper har rollen zipper i Bag Bag känner till en instans av klassen Container. Container har rollen container i Bag Zipper state : str = "open chance_to_break : float = 0.2 is_open() : boolean is_broken() : boolean open() close() try_to_break() zipper 1 Bag color : str = "green" open() close() add(item : str) container 1 Container items : list = [] capacity : int = 5 add(item : str)
Relationer i klassdiagram Relationer informerar oss om Vilka klasser känner till vilka andra klasser? Hur många instanser deltar i relationen? (Kardinalitet) Vilken roll har en klass i relation till en annan? Innebär att en instans av en klass har en referens till en instans av en annan klass (eller ytterligare en instans av den egna klassen) Exempel på referenser en instansvariabel låter oss referera till en enskild instans en lista/ett dictionary låter oss referera till flera instanser
Anm. kring användning av termen klass När vi säger "klassen C har en metod M" syftar vi oftast på "metoden M är definierad i klassen C" I praktiken innebär detta "för att använda M skapar vi en instans O av C och använder M hos O" Om Book och Page är klasser: "Book innehåller Page-objekt" ska tolkas som "Book-objekt innehåller Page-objekt", dvs "Instanser av klassen Book innehåller instanser av klassen Page", dvs "Instanser av klassen Book har en instansvariabel som refererar till instanser av klassen Page" Detta är naturligtvis inte så bra egentligen eftersom det kan leda till förvirring, så i denna kurs bör de långa varianterna användas.
Klassdiagram med UML Enkelriktade associationer (vi använder oss endast av associationsrelationer i denna kurs) Bag känner till en instans av klassen Zipper. Zipper har rollen zipper i Bag Bag känner till en instans av klassen Container. Container har rollen container i Bag Zipper state : str = "open chance_to_break : float = 0.2 is_open() : boolean is_broken() : boolean open() close() try_to_break() zipper 1 Bag color : str = "green" open() close() add(item : str) container 1 Container items : list = [] capacity : int = 5 add(item : str)
Demonstration
Vad vill vi göra? / Hur ska vi göra det? (Mönster) Exempel på "Vad vill vi göra?": 1. Dela upp ett problem 2. Delegera / skicka vidare uppgiften 3. Återanvända kod 4. Förenkla användning av kod Exempel på "Hur ska vi göra det?" 1a) Dela upp en funktion i flera funktioner 1b) Skapa olika klasser med olika ansvarsområden 2a) Anropa en annan funktion 2b) Anropa en metod i ett annat objekt 3a) bryta ut kod till en annan funktion 3b) skriva kod som en modul 3c) OOP: arv 4a) Dela upp problemet i olika abstraktionsnivåer. Hierarkisk struktur hos funktioner. 4b) Dela upp problemet i olika abstraktionsnivåer. Objekt som använder andra objekt
Exempel på problem Hur många filer finns under katalogen "Mina dokument" på min dator? Exempel på funktionell lösning: rekursion, traversera filstruktrukträdet Exempel på objektorienterad lösning: räkna antalet filer i egna katalogen, fråga varje underkatalog om hur många filer den har OOP: Vilka objekt har vi? Vilka frågor/uppgifter kan en instans som agerar som behållare delegera vidare till de instanser som den innehåller?
Delegering av uppgifter i verkliga världen
Illustrationsscenario: beställa biljett med hjälp av försäljare i forntiden "Hej, jag vill beställa en tågbiljett från Linköping till Stockholm med ankomst senast kl. 12 imorgon, sitter gärna vid mittgången." "Det finns ett tåg som åker kl. 10.18 och du är framme kl. 11.59, vill du åka med det?" "Det blir toppen." "Ok, här är din biljett"
Illustrationsscenario: beställa biljett utan någon form av hjälp i forntiden kolla upp tågtider hitta passande tåg ta fram karta över tåget för att se platsnummer ta listan med platser som är upptagna börja med första mittgångsplatsen, kontrollera om den är upptagen, fortsätt tills en ledig mittgångsplats hittats ta fram blank biljett från låda fyll i avreseort och destination skriv i listan med upptagna platser platsnummer samt biljettnummer fyll i platsnummer i biljetten klar
Vad vill vi göra? / Hur ska vi göra det? Exempel på "Vad vill vi göra?": 1. Dela upp ett problem 2. Delegera / skicka vidare uppgiften 3. Återanvända kod 4. Förenkla användning av kod Exempel på "Hur ska vi göra det?" 1a) Dela upp en funktion i flera funktioner 1b) Skapa olika klasser med olika ansvarsområden 2a) Anropa en annan funktion 2b) Anropa en metod i ett annat objekt 3a) bryta ut kod till en annan funktion 3b) skriva kod som en modul 3c) OOP: arv 4a) Dela upp problemet i olika abstraktionsnivåer. Hierarkisk struktur hos funktioner. 4b) Dela upp problemet i olika abstraktionsnivåer. Objekt som använder andra objekt
Exempel på olika sätt att delegera vidare uppgifter Arv och overloading, wrapper-metoder, aggregera information
Arv och overloading Exempel på att delegering av uppgift där "uppdragsgivaren" vill få något gjort, men inte riktigt bryr sig om hur Arv: en klass använder en annan klass som basklass - ärver egenskaper och beteenden. Exempel class Zipper(object) object är basklass för klassen Zipper Zipper är en härledd klass Overloading: Härledd klass definierar om en metod så att den används istället för basklassens. Exempel: när vi definierar metoderna init och str i egna klasser
Wrapper-metoder Wrapper-metod: Instanser av klassen A innehåller instanser av klassen B. En wrapper-metod är en metod i A vars enda syfte är att anropa på metoden i B Vi behöver bara känna till instanser av klassen A. A-instanserna "delegerar" vidare det vi vill utföra Exempel class Bag(object): def open(self): self.zipper.open() class Zipper(object): def open(self): print("trying to open zipper...") self.try_to_break() if not self.is_broken(): self.state = "open" print(self) else: print("cannot open zipper. {}".format(self))
Uppdelning av problem. Exempel på aggregering class City(object): def init (self, name): self.houses = [] self.name = name def add_house(self, house): self.houses.append(house) def get_population(self): total_population = 0 for house in self.houses: total_population += house.get_number_of_occupants() return total_population flaxtown = City("Linköping") house1 = House() house1.add_occupant("a") house1.add_occupant("b") house2 = House() house2.add_occupant("c") flaxtown.add_house(house1) flaxtown.add_house(house2) print(flaxtown) def str (self): city_str = "Name of city: {}\npopulation: {}" return city_str.format(self.name, self.get_population()) class House(object): def init (self): self.occupants = [] def add_occupant(self, name): self.occupants.append(name) def get_number_of_occupants(self): return len(self.occupants)
Exempel böcker i en bok-katalog
Exempel: Böcker i en bok-katalog Två egna klasser: Book, BookCatalogue Ett BookCatalogue-objekt kan innehålla flera Book-objekt
books1.py # Exempel där BookCatalogue inte ansvarar för något class Book(object): def init (self, title="untitled"): self.title = title def str (self): return "<Book: '{}'>".format(self.title) class BookCatalogue(object): def init (self): self.books = [] def str (self): books_as_strings = [] for book in self.books: books_as_strings.append(str(book)) return "BookCatalogue: {}".format(", ".join(books_as_strings)) if name == " main ": bc = BookCatalogue() new_book = Book("Book 1") bc.books.append(new_book) new_book = Book("Book 2") bc.books.append(new_book) new_book = Book("Book 3") bc.books.append(new_book) print(bc)
books2.py # Exempel där BookCatalogue tar hand om logistik när nya böcker ska läggas till class Book(object): def init (self, title="untitled"): self.title = title def str (self): return "<Book: '{}'>".format(self.title) class BookCatalogue(object): def init (self): self.books = [] def create_and_add_book(self, title): new_book = Book(title) self.books.append(new_book) def str (self): books_as_strings = [] for book in self.books: books_as_strings.append(str(book)) return "BookCatalogue: {}".format(", ".join(books_as_strings)) if name == " main ": bc = BookCatalogue() bc.create_and_add_book("book 1") bc.create_and_add_book("book 2") bc.create_and_add_book("book 3") print(bc)
books3.py # Exempel där BookCatalogue tar hand om att ha koll på bok-id mha instansvariabel class Book(object): def init (self, book_id, title="untitled"): self.book_id = book_id self.title = title def str (self): return "<Book#{}: '{}'>".format(self.book_id, self.title) class BookCatalogue(object): def init (self): self.books = [] self.book_counter = 0 def create_and_add_book(self, title): new_book = Book(self.book_counter, title) self.book_counter += 1 self.books.append(new_book) def str (self): books_as_strings = [] for book in self.books: books_as_strings.append(str(book)) return "BookCatalogue: {}".format(", ".join(books_as_strings)) if name == " main ": bc = BookCatalogue() bc.create_and_add_book("book 1") bc.create_and_add_book("book 2") bc.create_and_add_book("book 3") print(bc)
books4.py (överkurs) # Exempel där Book tar hand om att ha koll på bok-id mha klassvariabel class Book(object): next_book_id = 0 def init (self, title="untitled"): self.book_id = Book.next_book_id Book.next_book_id += 1 self.title = title def str (self): return "<Book#{}: '{}'>".format(self.book_id, self.title) class BookCatalogue(object): def init (self): self.books = [] def create_and_add_book(self, title): self.books.append(book(title)) def str (self): books_as_strings = [] for book in self.books: books_as_strings.append(str(book)) return "BookCatalogue: {}".format(", ".join(books_as_strings)) if name == " main ": bc = BookCatalogue() bc.create_and_add_book("book 1") bc.create_and_add_book("book 2") bc.create_and_add_book("book 3") print(bc)
Utökning Fler böcker i bok-katalog Flera bok-kataloger i ett bibliotek... flera bibliotek i en biblioteksorganisation osv
Interaktiva text-gränssnitt
Interaktionsloop med villkor och funktionsanrop def new_book(bookcatalogue): print("new_book() körs") def show_books(bookcatalogue): print("show_books() körs") def main(): bookcatalogue = BookCatalogue() user_input = "" while user_input!= "q": user_input = input("vad vill du göra? ").lower() if user_input == "ny": new_book(bookcatalogue) elif user_input == "visa": show_books(bookcatalogue) if name == ' main ': main()
Interaktionsloop med dictionary och funktionsobjekt def new_book(bookcatalogue): print("new_book() körs") def show_books(bookcatalogue): print("show_books() körs") def main(): commands = {} commands["ny"] = new_book commands["visa"] = show_books bookcatalogue = BookCatalogue() user_input = "" while user_input!= "q": user_input = input("vad vill du göra? ").lower() for command, cmd_func in commands.items(): if command == user_input: cmd_func(bookcatalogue) break if name == ' main ': main()
Interaktiv funktion som skapar ny bok def new_book(bookcatalogue): while True: confirmed = "" title = input("ange titel: ") while confirmed not in ["j", "n"]: confirmed = input("du skrev '{}', är det OK? [j/n]: ".format(title)) if confirmed == "j": bookcatalogue.create_and_add_book(title) break else: print("ok, skriv in igen!")
Utbruten bekräftelsefunktion def get_user_confirmation(message, yes, no): user_input = "" while user_input not in [yes, no]: user_input = input("{} [{}/{}]: ".format(message, yes, no)) if user_input == yes: return True else: return False def new_book(bookcatalogue): while True: confirmed = "" title = input("ange boktitel: ") message = "Du skrev '{}', är det OK?".format(title) user_confirmation = get_user_confirmation(message, "j", "n") if user_confirmation: bookcatalogue.create_and_add_book(title) break else: print("ok, skriv in igen!")
Instans som huvudprogram
Skript som vi brukar skriva dem from books4 import * def get_user_confirmation(message, yes, no): user_input = "" while user_input not in [yes, no]: user_input = input("{} [{}/{}]: ".format(message, yes, no)) if user_input == yes: return True else: return False def new_book(bookcatalogue): while True: confirmed = "" title = input("ange boktitel: ") message = "Du skrev '{}', är det OK?".format(title) user_confirmation = get_user_confirmation(message, "j", "n") if user_confirmation: bookcatalogue.create_and_add_book(title) break else: print("ok, skriv in igen!") def main(): commands = {} commands["ny"] = new_book commands["visa"] = show_books bookcatalogue = BookCatalogue() user_input = "" while user_input!= "q": user_input = input("vad vill du göra? ").lower() for command, cmd_func in commands.items(): if command == user_input: cmd_func(bookcatalogue) break if name == ' main ': main() def show_books(bookcatalogue): print(bookcatalogue)
Skript med instans som "program" from books4 import * def get_user_confirmation(message, yes, no): user_input = "" while user_input not in [yes, no]: user_input = input("{} [{}/{}]: ".format(message, yes, no)) if user_input == yes: return True else: return False class BookApp(object): def init (self): self.commands = {} self.commands["ny"] = self.new_book self.commands["visa"] = self.show_books self.bookcatalogue = BookCatalogue() self.main() def show_books(self): print(self.bookcatalogue) def main(self): user_input = "" while user_input!= "q": user_input = input("vad vill du göra? ").lower() for command, cmd_func in self.commands.items(): if command == user_input: cmd_func() break if name == ' main ': BookApp() def new_book(self): while True: confirmed = "" title = input("ange boktitel: ") message = "Du skrev '{}', är det OK?".format(title) user_confirmation = get_user_confirmation(message, "j", "n") if user_confirmation: self.bookcatalogue.create_and_add_book(title) break else: print("ok, skriv in igen!")