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.
Lärandemål
- kunna ge exempel och förklara hur ett programmeringsproblem kan brytas ner när man använder sig av objektorienterad programmering (t.ex. delegering av
uppgifter, ansvarsuppdelning m.m. se föreläsning)
- kunna peka ut exempel i egen objektorienterad kod på hur ett problem brutits ner i mindre delar
- kunna skriva ett objektorienterat program med egna klasser där instanser av en egenskriven klass lagras i en instans av en annan egenskriven klass
- kunna skriva ett skript med interaktivt text-gränssnitt med felhantering
Krav för godkänd kod på Del 1-2
- 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.
Del 0: SMoL Tutor 5-8 (enskilt)
I samband med Laboration 5-7 skall ni individuellt göra övningarna i SMoL Tutor som ni finner här. SMoL Tutor är ett system för att öva på att läsa och förstå kod samt för hjälpa er att bygga upp en mental modell över vad som händer under ytan när Python-kod körs. Övningarna tar i genomsnitt 10-20 minuter vardera och ingen enskild övning bör ta mer än 30 minuter att genomföra.
Verktyget är tillgängligt på engelska och era fullt anonymiserade svar kan komma att användas i kursutvecklingssyfte. Era anonymiserade svar kan eventuellt också komma att användas i forskningssyfte, om ni inte vill att era svar ska kunna användas i forskningssyfte så kontakta examinator. Övningarna är obligatoriska att genomföra, men det är inte tänkt att ni ska arbeta med dem under schemalagd labbtid då ni istället förväntas arbeta tillsammans med er labbpartner på de gemensamma delarna av labben.
Övningarna är:
- Definitions
- More definitions
- Even more definitions
- Mutable arrays
- More mutable arrays
- Even more mutable arrays
- Mutable variables
- More mutable variables
- Functions as values
- More about functions as values
- Lambda expressions
Ni kan göra övningarna när som helst under VT2, men i Webreg rapporteras de i grupper 1-4, 5-8 och 9-11 som är rekommenderat att göra i samband med Laboration 5, 6 respektive 7.
I samband med Laboration 6 bör ni åtminstone arbeta er igenom del 5-8.
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
Token
representerar 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
.
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 3 uppgifter. Lös uppgifterna i ordning.
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
, och motsvarande för uppgift 3.
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)
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
get_id
returnerar instansens ID
is_done
returnerar huruvida instansen är markerad 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
.
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.
Uppdaterat 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.
Fundera över följande frågor:
- Vad är lämpliga namn på relationerna
X
och Y
?
- Vad innebär det att relationerna
Y
och Z
delar linje?
- Behöver
Z
vara en namngiven relation och vad skulle det innebära om så var fallet?
Viktigt! Håll kod som ansvarar för interaktion med användaren separat från kod som modellerar data.

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):
Utöka funktionaliteten så att ni kan ge uppgifter deluppgifter.
Kopiera era filer för Del 1, Uppgift 2 och ge filerna som hör ihop med denna extrauppgift suffixet _uppg3
, t.ex. del2_uppg3.py
.
Krav på funktionalitet för Uppgift 3
- Lägg till ett kommando för att skapa en deluppgift.
- Deluppgifter skall representeras som
Task
-objekt på samma sätt som vanliga uppgifter.
- Dvs. även deluppgifter kan i sin tur ha deluppgifter, osv. och vi kan tänka på detta som en typ av nästling eller trädstruktur.
- När en uppgift som har deluppgifter klarmarkeras så ska alla dess deluppgifter, till godtycklig nivå av nästling (dvs. deluppgifter till deluppgifter, osv.), också markeras som klara oavsett om de tidigare markerats som klara eller ej.
- Lägg till ett kommando för att lista alla deluppgifter, på godtycklig nivå av nästling, till en uppgift. Uppgiften och dess deluppgifter ska visas på ett sätt som illustrerar hur de hänger samman (se exempel nedan).
- Fundera över hur 2. och 3. ovan skulle kunna implementeras som en högre ordningens funktion. Hur skulle den kunna fungera? Hur skulle de funktionsobjekt som denna högre ordningens funktion använder för att utföra 2. respektive 3. kunna se ut?
Tips
Ni bör använda er av rekursion för att hantera nästlade Task
-objekt.
De flesta Task
-objekt kommer vara både huvuduppgifter och deluppgifter, på samma sätt som en person i ett familjeträd oftast har både föräldrar och barn.
Alla Task
-objekt bör innehålla en lista med Task
-objekt som representerar dess deluppgifter. Om en uppgift inte har några deluppgifter kommer den listan helt enkelt att vara tom.
Alla Task
-objekt bör också ha en referens till sin huvuduppgift. Om ett Task
-objekt inte har någon huvuduppgift låter man den referensen vara None
.
Lämpliga metoder där man kan vilja använda någon form av rekursion är t.ex. i Task.mark_done
och TodoApp.show_subtasks
.
Det kan vara lämpligt att låta metoden TodoApp.show_subtasks
främst hantera interaktionen med användaren och låta en separat metod eller nästlad funktion göra själva utskriften (t.ex. TodoApp._print_subtasks
). Det är i så fall denna andra metod eller nästlade funktion som bör vara rekursiv.
För att den rekursiva funktionen/metoden som sköter utskriften ska hantera indenteringsdjupet korrekt kan man med fördel låta denna ta ett argument (som binds till t.ex. en parameter depth
) som multipliceras med strängen '├──'
i början av varje utskrift.
Uppdaterat klassdiagram
Programmet bör använda nedanstående klasser och de angivna relationerna kan finnas mellan klasserna. Klasserna bör 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.

OBS! All kod som som har med användargränssnittet att göra ska fortfarande endast ligga i klassen TodoApp.
Exempelkörningar för utökning
Lägg till deluppgifter
Ange kommando (q=avsluta, ?=hjälp): ?
Kommandon: ny uppgift, ny deluppgift, ny användare, visa uppgifter, visa deluppgifter, visa användaruppgifter, visa användare, klar, ?
Ange kommando (q=avsluta, ?=hjälp): visa användare
ada
bertil
cecilia
Ange kommando (q=avsluta, ?=hjälp): visa uppgifter
0. [ ] baka kakor (cecilia)
1. [ ] tvätta (ada)
Ange kommando (q=avsluta, ?=hjälp): ny deluppgift
Ange huvuduppgift: 0
Beskriv uppgiften: handla ingredienser
Användare: cecilia
Ange kommando (q=avsluta, ?=hjälp): visa uppgifter
0. [ ] baka kakor (cecilia)
1. [ ] tvätta (ada)
2. [ ] handla ingredienser (cecilia) - deluppgift till 0
Klarmarkera uppgifter med deluppgifter
Ange kommando (q=avsluta, ?=hjälp): klar
0. [ ] baka kakor (cecilia)
1. [ ] tvätta (ada)
2. [ ] handla ingredienser (cecilia) - deluppgift till 0
3. [ ] torka (bertil) - deluppgift till 1
4. [ ] göra kaksmet (cecilia) - deluppgift till 0
5. [ ] grädda kakor (cecilia) - deluppgift till 0
6. [ ] vika (ada) - deluppgift till 1
7. [ ] tumla (bertil) - deluppgift till 3
8. [ ] hänga (bertil) - deluppgift till 3
Vilken uppgift ska markeras som klar? 1
Ange kommando (q=avsluta, ?=hjälp): visa uppgifter
0. [ ] baka kakor (cecilia)
1. [X] tvätta (ada)
2. [ ] handla ingredienser (cecilia) - deluppgift till 0
3. [X] torka (bertil) - deluppgift till 1
4. [ ] göra kaksmet (cecilia) - deluppgift till 0
5. [ ] grädda kakor (cecilia) - deluppgift till 0
6. [X] vika (ada) - deluppgift till 1
7. [X] tumla (bertil) - deluppgift till 3
8. [X] hänga (bertil) - deluppgift till 3
Visa deluppgifter till en uppgift
Ange kommando (q=avsluta, ?=hjälp): visa deluppgifter
Ange huvuduppgift: 1
1. [ ] tvätta (ada)
├──3. [ ] torka (bertil) - deluppgift till 1
├──├──7. [ ] tumla (bertil) - deluppgift till 3
├──├──8. [ ] hänga (bertil) - deluppgift till 3
├──6. [ ] vika (ada) - deluppgift till 1
Uppgift 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.
- Berätta vilka instansvariabler och metoder ni själva lagt till era klasser.
- Gå igenom er kod och förklara var instanser av de olika klasserna skapas. Ni behöver inte gå igenom all kod ni skrivit.
- 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
- Kör koden för Uppgift 2.
- Förklara hur
User
-klassen används i er kod.
- Besvara frågorna om UML-diagrammet och förklara hur ni tänker. Här kommer de igen:
- Vad är lämpliga namn på relationerna
X
och Y
?
- Vad innebär det att relationerna
Y
och Z
delar linje?
- Behöver
Z
vara en namngiven relation och vad skulle det innebära om så var fallet?
- 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?
Uppgift 3
- Kör koden för Uppgift 3.
- Visa hur deluppgifter representeras i
Task
-klassen.
- Visa hur en struktur av uppgifter och deluppgifter hanteras vid klarmarkering och utskrift.
- Redogör för hur ni tänker kring att implementera klarmarkering och utskrift med hjälp av en högre ordningens funktion.