Göm meny
Gäller för: VT24

Laboration 6: Objektorienterad problemuppdelning

I laboration 6 kommer ni fortsätta öva på att skapa egna klasser. Denna gång ska ni dock lagra instanser av klasser i andra instanser, samt skapa metoder som delegerar vidare den uppgift som ska lösas.

Redovisning och kompletteringar

  • Information om den muntliga redovisningen, samt eventuella kompletteringar kan ni läsa om på sidan Redovisning.
  • Vid inlämning av era filer, se till att ni laddar upp *alla era filer med kod. Se även till att allt fortfarande fungerar om ni har fått komplettering för något och ändrat i koden sedan redovisningstillfället.

Checklistor att gå igenom vid redovisning

Vid redovisning på distans ska ni gå igenom nedanstående checklistor för den assistent ni redovisar för.

Redovisning Del 1

  1. Visa att ni kan köra ert program och att pycodestyle och pydocstyle inte ger några anmärkningar.
  2. Berätta vilka instansvariabler och metoder ni själva lagt till era klasser.
  3. Gå igenom er kod och förklara var instanser av de olika klasserna skapas.
  4. Gå igenom er kod och förklara hur flödet ser ut när antalet tecken i en mening ska tas fram.

Redovisning Del 2

Uppgift 1

  1. Visa att ni kan köra ert program, att programmet uppfyller de krav på funktionalitet som ställts och att pycodestyle och pydocstyle inte ger några anmärkningar.
  2. Berätta vilka instansvariabler och metoder ni själva lagt till era klasser.
  3. Gå igenom er kod och förklara var instanser av de olika klasserna skapas. Ni behöver inte gå igenom all kod ni skrivit.
  4. Assistenten ni redovisar för kommer att be er redogöra antingen för flödet för när en ny uppgift skapas och/eller flödet för när en uppgift markeras som klar.

Uppgift 2

  1. Kör koden för Uppgift 2.
  2. Visa ert uppdaterade klassdiagram, förklara hur User-klassen används.
  3. Gå igenom de tillägg ni gjort till koden:
    • var används User-klassen
    • vad görs för att bara visa uppgifter som hör ihop med en specifik användare

Krav för godkänd kod

  • Koden ska följa PEP 8.
  • Moduler (själva filen), funktioner, klasser och metoder ska kommenteras enligt PEP 257.
  • Koden ska följa koddispositionen nedan.
  • Koden ska utföra uppgifterna enligt respektive uppgiftsbeskrivning.
  • Koden ska vara uppdelad i olika filer enligt instruktionerna för respektive uppgift.

För hjälp med kontroll av PEP, se PEP 8 och PEP 257.

Krav på koddisposition

För att göra det lättare att hitta i koden är det bör den följa en genomtänkt koddisposition. Nedan är en vanlig disposition som används i många språk.

  1. Alla import-satser placeras högst upp i filen
  2. Eventuella globala konstanter
  3. Funktionsdefinitioner
  4. Kod utanför funktioner, t.ex. anrop till en main()-funktion.

Krav på kommentarer och namngivning av funktioner och variabler

  • Alla satser som består av fler än en rad ska ha en tillhörande kommentar. Dvs if-satser, loopar, funktioner och metoder. Kommentaren ska inte vara en “innantilläsning” av koden den hör till. Använd rätt kommentarsmarkering (t.ex. """ för dokumentationssträngar, # för kommentarer i löpande kod, se PEP 8 och PEP 257).
  • Funktioner och variabler ska vara döpta på ett bra sätt, dvs att de är beskrivande och reflekterar innehåll/funktion. Låt funktionsnamn vara verb och variabelnamn vara substantiv. Undvik namn på enbart en bokstav (var beredda att kunna motivera varför ni valt just det namnet).

Här är icke-godkänd kod:

v = 0
q = 0
u = [19, 21, 24, 20]

# för alla element t i u, lägg till t till v
for t in u:
    v += t
q = v/len(u)

Här är en godkänd variant

sum_age = 0
avg_age = 0
ages = [19, 21, 24, 20]

# räkna ut summan av alla åldrar
for age in ages:
    sum_age += age
avg_age = sum_age/len(ages)

Försök att tänka på

  • Minimera användningen av hårdkodade siffror, (s.k. magic numbers). Använd istället variabler med förklarande namn.
  • Dela upp er kod i delfunktioner/delmetoder vid behov:
    • när ni stöter på kod som ni upprepar, bryt ut koden (lägg koden i en funktion/metod)
    • dela även upp er kod i fler funktioner/metoder när den blir för lång

Del 1: Representation av text

Här ska ni skapa tre klasser som representerar olika beståndsdelar av en text. Varje klass tar hand om uppgifter på sin nivå och delegerar vidare uppgifter till instanser på lägre nivåer.

Utskrift

När ni läst in texten och skapar instanserna skriver ni ut något i stil med nedanstående. Mellanslag och radbrytningar behöver inte räknas in i antalet tecken (olika utskriftsalternativ finns längre ner på sidan).

Texten innehåller 15 meningar, 133 ord/skiljetecken, 676 tecken.
Mening 1 innehåller 22 ord/skiljetecken (105 tecken)
Mening 2 innehåller 7 ord/skiljetecken (35 tecken)
Mening 3 innehåller 8 ord/skiljetecken (44 tecken)
Mening 4 innehåller 6 ord/skiljetecken (32 tecken)
Mening 5 innehåller 6 ord/skiljetecken (33 tecken)
Mening 6 innehåller 8 ord/skiljetecken (35 tecken)
Mening 7 innehåller 8 ord/skiljetecken (44 tecken)
Mening 8 innehåller 7 ord/skiljetecken (29 tecken)
Mening 9 innehåller 10 ord/skiljetecken (57 tecken)
Mening 10 innehåller 10 ord/skiljetecken (47 tecken)
Mening 11 innehåller 9 ord/skiljetecken (44 tecken)
Mening 12 innehåller 6 ord/skiljetecken (34 tecken)
Mening 13 innehåller 10 ord/skiljetecken (50 tecken)
Mening 14 innehåller 7 ord/skiljetecken (41 tecken)
Mening 15 innehåller 9 ord/skiljetecken (46 tecken)

Klasser

Följande klasser ska skapas:

  • Klassen Tokenrepresenterar ett ord eller ett skiljetecken och ska ha en sträng som instansvariabel som innehåller ordet/skiljetecknet. En instans skapas med Token(value) där value är en sträng med är ett ord eller ett skiljetecken. I den här labben räknas allt som föregås eller följs av ett mellanslag eller en radbrytning som ett token. Dvs. även punkter och komman är tokens och skall representeras av Token-objekt.
  • Klassen Sentence representerar en mening och ska innehålla instanser av klassen Token. En instans skapas med Sentence(value) där value är en sträng med en mening. Det är tillåtet att göra det förenklande antagandet att varje rad i den givna texten nedan motsvarar exakt en mening.
  • Klassen Text representerar hela texten och ska innehålla instanser av klassen Sentence. En instans skapas med Text(value) där value är en sträng som innehåller hela texten (se nedan). OBS! Ska inte känna till instanser av Token.

Informationen kommer alltså att lagras på ett trädstrukturliknande sätt. Notera att själva strängarna med ord eller skiljetecken endast lagras i instanser av klassen Token.

Klassdiagram

Nedan hittar ni skiss på klassdiagram. Alla instansvariabler/metoder är inte med, utan ni kommer behöva lägga till ytterligare metoder (och kanske instansvariabler) till era klasser.

  • Text.get_num_sentences() ska returnera antalet meningar i texten (dvs antalet Sentence-instanser)
  • Text.get_num_tokens() ska aggregera det totala antalet tokens för hela texten genom att fråga sina Sentence-instanser hur många tokens de har.
  • Text.get_num_chars() ska aggregera det totala antalet tecken för hela texten genom att fråga sina Sentence-instanser hur många tecken de har.
  • Sentence.get_num_tokens() ska returnera antalet tokens i meningen (dvs antalet Token-instanser)
  • Sentence.get_num_chars() ska aggregera det totala antalet tecken för meningen genom att fråga sina Token-instanser hur många tecken de har.
  • Token.get_num_chars() ska returnera antalet tecken som Token-instansen har.

Metoder som t.ex. __str__() och __init__() ritas normalt inte ut i klassdiagram. Dessa ska dock implementeras där det är lämpligt.

Utskrift av Text-objektet

Målet i denna uppgift är att utskriften ovan ska ske genom ett enda anrop till print(text) (där text är ett Text-objekt). När ett Text-objekt returnerar en sträng-representation av sig själv (via __str__()-metoden) behöver det fråga sina Sentence-objekt t.ex. hur många ord och hur många tecken som de innehåller.

Tips: Se exemplet på aggregering från föreläsningen.

Utskriftsalternativ (olika svårighetsgrader)

Välj själva vilken utskrift ni vill göra. Mellanslag behöver inte räknas in i antalet tecken.

Lättare variant

Texten innehåller 15 meningar.
Texten innehåller 133 ord/skiljetecken.
Texten innehåller 676 tecken.

Svårare variant

Texten innehåller 15 meningar, 133 ord/skiljetecken, 676 tecken.
Mening 1 innehåller 22 ord/skiljetecken (105 tecken)
Mening 2 innehåller 7 ord/skiljetecken (35 tecken)
Mening 3 innehåller 8 ord/skiljetecken (44 tecken)
Mening 4 innehåller 6 ord/skiljetecken (32 tecken)
Mening 5 innehåller 6 ord/skiljetecken (33 tecken)
Mening 6 innehåller 8 ord/skiljetecken (35 tecken)
Mening 7 innehåller 8 ord/skiljetecken (44 tecken)
Mening 8 innehåller 7 ord/skiljetecken (29 tecken)
Mening 9 innehåller 10 ord/skiljetecken (57 tecken)
Mening 10 innehåller 10 ord/skiljetecken (47 tecken)
Mening 11 innehåller 9 ord/skiljetecken (44 tecken)
Mening 12 innehåller 6 ord/skiljetecken (34 tecken)
Mening 13 innehåller 10 ord/skiljetecken (50 tecken)
Mening 14 innehåller 7 ord/skiljetecken (41 tecken)
Mening 15 innehåller 9 ord/skiljetecken (46 tecken)

Text att använda

Använd nedanstående text för att testa era klasser. Ni hittar en textfil som innehåller nedantående text i /courses/TDDE44/kursmaterial/laboration6/del1/lorem.txt. I den textfilen är varje rad en mening.

Lorem ipsum dolor sit amet , consectetur adipiscing elit , sed do eiusmod tempor incididunt ut labore et dolore magna aliqua .
At lectus urna duis convallis convallis .
Ut faucibus pulvinar elementum integer enim neque .
In hendrerit gravida rutrum quisque .
Purus ut faucibus pulvinar elementum .
Nibh tortor id aliquet lectus proin nibh .
Nulla porttitor massa id neque aliquam vestibulum .
Cras ornare arcu dui vivamus arcu .
Porttitor massa id neque aliquam vestibulum morbi blandit cursus .
Felis imperdiet proin fermentum leo vel orci porta non .
Fermentum dui faucibus in ornare quam viverra orci .
Pulvinar elementum integer enim neque .
Id neque aliquam vestibulum morbi blandit cursus risus at .
Consectetur lorem donec massa sapien faucibus .
Amet massa vitae tortor condimentum lacinia quis vel .

Kodstruktur

Definiera klasserna i en fil som ni kallar för del1.py. Efter klassdefinitionerna skriver ni den kod ni behöver för att testa klasserna skyddat bakom ett villkor som kontrollerar för __name__ == "__main__":

# eventuella importer

# definition av klassen Token

# definition av klassen Sentence

# definition av klassen Text

if __name__ == "__main__":
    # test av klasser

Tips för implementationen

  • Börja med det minsta problemet, klassen Token. Definiera klassen och skriv kod som skapar en instans av den klassen: token = Token("Hej")
  • Så fort ni lägger till funktionalitet, se till att testa er kod.
  • Jobba er upp från Token till Sentence och implementera Text sist.

Fortsätt sedan med klassen Sentence. Definiera klassen och skriv kod som skapar en instans av den klassen: sentence = Sentence("Mor ser far .").

Jobba med __init__-metoden i Sentence så att den

  • delar upp meningen i ord/skiljetecken
  • skapar en instans av klassen Token för varje ord/skiljetecken

För den svårare varianten, låt varje instans av Sentence ha en instansvariabel som innehåller information om vilket meningsnummer den har.

För den svårare varianten, jobba med __str__-metoden i Sentence så att den returnerar "X ord/skiljetecken (Y antal tecken)" (med rätt värden på X och Y), vilken plats meningen har kan Text-klassen ta hand om i sin __str__().

Använd strängmetoden str.split(separator) för att göra om en sträng till en lista som innehåller varje delsträng som i den ursprungliga strängen skiljdes åt av strängen separator.

Se även Skriva ut en lista som innehåller egendefinerade klasser.

Del 2

I del 2 ska ni skriva ett objektorienterat skript som låter användaren skriva in uppgifter att göra (en att-göra-lista) via ett text-gränssnitt.

Del 2 består av två uppgifter. Lös Uppgift 1 innan ni börjar på Uppgift 2.

Uppgift 1: Att-göra-program

För att hålla isär filerna som hör ihop med Uppgift 1 från de som hör ihop med Uppgift 2, ge filerna för uppgift 1 suffixet _uppg1, t.ex. del2_uppg1.py. Ni ger sedan filerna som hör ihop med uppgift 2 suffixet _uppg2, t.ex. del2_uppg2.py.

Krav på funktionalitet

Med skriptet ska användaren kunna göra följande:

  • visa vilka kommandon som användaren kan skriva
  • visa listan med saker som finns att göra, där varje uppgift har ett unikt ID, samt visa om uppgiften är gjord eller inte
  • kunna markera en uppgift som klar
  • avsluta skriptet med ett kommando
  • skriptet ska inte krascha vid felaktig input

Se föreläsningsbilderna för tips både skapande av text-gränssnittet och objektorienterad struktur.

Viktigt! Håll kod som ansvarar för interaktion med användaren separat från kod som modellerar data.

Exempel på körning

Nedan är ett exempel som visar hur det kan se ut när skriptet körs. Er version behöver inte se exakt likadan ut så länge som den uppfyller kraven ovan.

$ ./del2.py
Ange kommando (q=avsluta, ?=hjälp): ?
Kommandon: ny, visa, klar, ?
Ange kommando (q=avsluta, ?=hjälp): ny
Beskriv uppgiften: Tvätta
Du skrev 'Tvätta', är det OK? [j/n]: j
Ange kommando (q=avsluta, ?=hjälp): ny
Beskriv uppgiften: vika tvätt
Du skrev 'vika tvätt', är det OK? [j/n]: j
Ange kommando (q=avsluta, ?=hjälp): visa
  0. [ ] Tvätta
  1. [ ] vika tvätt
Ange kommando (q=avsluta, ?=hjälp): klar
  0. [ ] Tvätta
  1. [ ] vika tvätt
Vilken uppgift ska markeras som klar? 1
Ange kommando (q=avsluta, ?=hjälp): visa
  0. [ ] Tvätta
  1. [X] vika tvätt
Ange kommando (q=avsluta, ?=hjälp): q

Klassdiagram

Programmet ska använda nedanstående klasser och de angivna relationerna ska finnas mellan klasserna. Klasserna ska innehålla minst de angivna metoderna och instansvariablerna.

Viktigt! Håll kod som ansvarar för interaktion med användaren separat från kod som modellerar data.

Metoder som t.ex. __str__() och __init__() ritas normalt inte ut i klassdiagram. Dessa ska dock implementeras där det är lämpligt.

Klassen TodoApp

OBS! Det är klassen TodoApp som innehåller all kod som har med det textbaserade gränsnittet att göra.

  • show_commands() skriver ut vilka kommandon som användaren kan använda
  • new_task() frågar användaren vilken uppgift som ska läggas ber TaskList-instansen lägga till den
  • show_tasks() visar uppgifterna
  • mark_done() frågar användaren vilken uppgift som ska markeras som klar och ser till att det blir gjort
  • main() innehåller interaktionsloopen och anropas sist från __init__() när en instans av TodoApp skapas

Klassen TaskList

Klassen TaskList är vår modell för en lista av uppgifter och används för att representera data för en uppgiftslista. Klassen lagrar godtyckligt många instanser av klassen Task. För läsbarhetens skull anges inte metodargumenten i listan nedan (se klassdiagrammet ovan istället).

  • task_counter håller koll på hur många instanser av Task som skapats (alternativt kan en klassvariabel i Task användas (se föreläsningsbilder))
  • create_task() skapar och sparar en instans av Task.
  • mark_done() ändrar rätt instans av Task så att den blir markerad som klar (använd metoden mark_done() i Task`).

Ingen kod som direkt bidrar till det textbaserade användargränssnittet får skrivas i klassen TaskList. Dvs ingen kod som ställer en fråga till användaren som ska besvaras får skrivas i någon metod i TaskList. All interaktionskod ska finnas i klassen TodoApp.

Klassen Task

Klassen Task är vår modell för en uppgift och används för att representera data för en uppgift som t.ex. dess beskrivning och om den är utförd eller inte.

  • task_id unikt ID för en uppgift, visas när uppgifter visas för användaren
  • description beskrivning av uppgiften
  • done får värdet True om uppgiften är utförd
  • mark_done() markerar den egna instansen som klar

Ingen kod som direkt bidrar till det textbaserade användargränssnittet får skrivas i klassen Task. Dvs ingen kod som ställer en fråga till användaren som ska besvaras får skrivas i någon metod i Task. All interaktionskod ska finnas i klassen TodoApp.

Alla klasser ska ha en __str__()-metod

Varje klass ska ha en __str__()-metod som används när objekten ska skrivas ut.

OBS! __str__() anropas aldrig explicit utan den används genom att konvertera ett objekt till en sträng, eller när ett objekt skrivs ut med print().

Uppgift 2: Utöka programmet med användare

När ni är klara med ert program för Uppgift 1, utöka funktionaliteten så att ni kan skapa användare i programmet, samt ange användare för uppgifter. Kopiera era filer för Del 2, Uppgift 1 och ge filerna som hör ihop med uppgift 2 suffixet _uppg2, t.ex. del2_uppg2.py.

Krav på funktionalitet för Uppgift 2

  • Lägg till en klass för användare, klassen User.
  • Lägg till kommando för att skapa en användare. Det ska inte gå att skapa flera användare med samma användarnamn.
  • Lägg till kommando för att visa skapade användare.
  • Ändra i kommandot för att lägga till en uppgift så att man även måste ange användaren som ska utföra uppgiften.
  • Ändra i kommandot för att visa uppgifter så att den också visar vilken användare som blivit tilldelad till den uppgiften.
  • Lägg till kommando för att visa vilka uppgifter som en viss användare har blivit tilldelad.

Uppdatera klassdiagram

Innan ni börjar skriva kod, uppdatera klassdiagrammet och visa för en assistent. Ni kan rita klassdiagrammet på papper eller utöka det existerande klassdiagrammet i Mermaid. Ett tredje alternativ är att använda t.ex. draw.io.

Se till att spara URL:en om ni ändrar på klassdiagrammet i Mermaid. Ni kan t.ex. lägga till det som kommentar i någon av era filer för uppgift 2.

Bild på ert klassdiagram skall bifogas till inlämningen. I Mermaid finns en knapp för att ladda ner en png-bild under Actions-menyn. I draw.io kan man Klicka på File $\rightarrow$ Export as $\rightarrow$ PNG.

OBS! All kod som som har med användargränssnittet att göra ska fortfarande endast ligga i klassen TodoApp.

Exempelkörning för utökning

Nedan är ett exempel som visar hur det kan se ut när skriptet körs. Er version behöver inte se exakt likadan ut så länge som den uppfyller kraven ovan.

Ange kommando (q=avsluta, ?=hjälp): ?
Kommandon: ny uppgift, ny användare, visa uppgifter, visa användaruppgifter, 
  visa användare, klar, ?
Ange kommando (q=avsluta, ?=hjälp): ny uppgift
Beskriv uppgiften: tvätta
Inga användare finns, avbryter.
Ange kommando (q=avsluta, ?=hjälp): ny användare
Namn för användaren: ada
Ange kommando (q=avsluta, ?=hjälp): ny användare
Namn för användaren: bertil
Ange kommando (q=avsluta, ?=hjälp): ny användare
Namn för användaren: bertil
Användaren bertil finns redan.
Namn för användaren: cecilia
Ange kommando (q=avsluta, ?=hjälp): visa användare
Användare:
ada
bertil
cecilia
Ange kommando (q=avsluta, ?=hjälp): ny uppgift
Beskriv uppgiften: tvätta
Användare: ada
Ange kommando (q=avsluta, ?=hjälp): ny uppgift
Beskriv uppgiften: vika tvätt
Användare: bert
Felaktigt användarnamn!
Användare: bertil
Ange kommando (q=avsluta, ?=hjälp): visa uppgifter
  0. [ ] tvätta (ada)
  1. [ ] vika tvätt (bertil)
Ange kommando (q=avsluta, ?=hjälp): klar
  0. [ ] tvätta (ada)
  1. [ ] vika tvätt (bertil)
Vilken uppgift ska markeras som klar? 0
Ange kommando (q=avsluta, ?=hjälp): visa uppgifter
  0. [X] tvätta (ada)
  1. [ ] vika tvätt (bertil)
Ange kommando (q=avsluta, ?=hjälp): visa användaruppgifter
Användare: bertil
Uppgifter för användaren bertil:
  1. [ ] vika tvätt (bertil)
Ange kommando (q=avsluta, ?=hjälp):

Frivilliga extrauppgifter

Flera uppgiftslistor

I klassdiagrammet och uppgiftsbeskrivningen ovan har varje TodoApp-instans endast en `TaskList-instans. Utöka funktionaliteten hos programmet så att man kan skapa flera uppgiftslistor. Det betyder att ni behöver

  • lägga till en instansvariabel så att TaskList-instanserna har namn
  • ändra på TodoApp-klassen så att den kan lagra en eller flera instanser av TaskList
  • lägga till ett kommando (och tillhörande funktionalitet) för att skapa nya TaskList-instanser
  • lägga till ett kommando för att visa vilka TaskLists som finns
  • ändra på kommandona som jobbar med Task-instanser, så att man även anger vilken TaskList-instans man vill jobba med.

Skapa ett uppdaterat UML-diagram som har med de ändringar ni gjort.

Spara och ladda data

Utöka funktionaliteten i programmet så att en användare kan spara och ladda data. Det är fritt för er att välja format som sparad data ska ha (t.ex. någon form av textbaserat format eller om ni använder Pythons pickle-modul). Ni behöver alltså göra följande:

  • bestämma filformat för sparad data
  • lägga till ett kommando för att läsa in data från sparad fil
  • lägga till ett kommando för att spara data till fil

Skapa även ett nytt UML-diagram som har med de ändringar ni gjort.


Sidansvarig: Johan Falkenjack
Senast uppdaterad: 2024-04-17