Översikt
Denna vecka jobbar du vidare med objektorienterade konstruktioner och tittar bland annat på arv. Information om veckans upplägg annonseras löpande i kursens Zulip-kanal.
Veckostart med A3
Section titled “Veckostart med A3”Tisdagen i kursvecka 7 handlade mest om examinationsuppgift A3: det som är nytt i veckans presentationer om kommandoradsargument och statuskoder, hur klassdiagrammet ska läsas, i vilken ordning du kan bygga klasserna och varför det inte finns några givna tester. Därefter följde en lägesrapport om A2 och frågor om ramverk, kommande kurser och hur du ställer frågor. Arvet sparades till torsdagen.
Om passet
Section titled “Om passet”Tisdagens pass i kursvecka 7 hölls av Mats och Mikael tillsammans. Veckans tema är arv, men själva genomgången av arv sparades till torsdagen. I stället gick Mats igenom veckans material, framför allt det som är nytt i presentationerna inför årets kursomgång, och examinationsuppgift A3: klassdiagrammet, i vilken ordning du kan bygga klasserna och att det inte finns några givna tester. Passet avslutades med en lägesrapport om A2 och frågor från chatten om ramverk, kommande kurser och hur du ställer frågor.
Varför arv?
Section titled “Varför arv?”Arv är ett sätt att återanvända kod, ett av många. Kursen går inte på djupet, men du behöver grunderna längre fram: i kursvecka 10 kommer webbkomponenter, som bygger på arv, och arv återkommer i Java-kursen i läsperiod 3. I A3 behövs arv bara om du väljer den utmanande varianten.
Mikael tonar ner det hela. Själv lärde han sig en gång att arv var den stora grejen med objektorientering, men det är bara en av många kodkonstruktioner. Den är bra för återanvändning i vissa sammanhang, men inget konstigare än så.
Reviderade presentationer
Section titled “Reviderade presentationer”Presentationen om inkapsling är uppdaterad så att den använder samma begrepp som Mikaels tärningsexempel från kursvecka 6. Den skiljer sig därför något från inspelningen, som publicerades 2021. Detsamma gäller presentationen om arv.
Störst är tillägget i presentationen om validering och strukturerad felhantering. Genom åren har det kommit många frågor om hur kraven i A3 ska tolkas, och därför har presentationen fått några bilder som inspelningen inte tar upp alls. De handlar om två saker du behöver i A3: kommandoradsargument och att avsluta en applikation med en statuskod.
Kommandoradsargument
Section titled “Kommandoradsargument”Hittills har du skickat information till funktioner och metoder. Men du kan också skicka information
till en applikation när du startar den, i stället för bara npm start. Det kallas
kommandoradsargument, och du når dem via arrayen process.argv. De två första elementen är
sökvägen till node och till skriptet, och därefter kommer argumenten, alltid som strängar.
Exemplet i presentationen summerar två tal. npm start 37 5 ska ge 42. Men vad ska hända om du
inte skickar med två tal? Precis som med argument till en konstruktor eller en setter behöver du
validera:
const [, , first, second] = process.argv.map(Number)
if (process.argv.length !== 4 || Number.isNaN(first) || Number.isNaN(second)) { throw new Error('Two numbers are required.')}
console.log(first + second)I A3 är det antalet spelomgångar och antalet spelare som skickas in, t.ex. npm start 4 7, och båda
ska valideras.
Avsluta med en statuskod
Section titled “Avsluta med en statuskod”I A3 ska applikationen också tala om hur det gick när den avslutas, med en statuskod (exit code).
Node.js sätter själv statuskoden till 0 om allt gått bra, och till 1 om ett undantag inte
hanteras. Vill du sätta en egen kod finns process.exit(), men den avslutar applikationen direkt.
Det föredragna sättet är att sätta process.exitCode och låta Node.js avsluta applikationen på
vanligt sätt.
Det nya i presentationen är hur valideringen och statuskoden knyts ihop med en egen felklass. Den
ärver från Error, så här dyker veckans tema upp redan:
export class InvalidArgumentError extends Error { constructor(message) { super(message) this.name = 'InvalidArgumentError' this.errorCode = 2 }}try { // ...validation that throws InvalidArgumentError...} catch (err) { console.error(err.message) process.exitCode = Number.isInteger(err.errorCode) ? err.errorCode : 1}Har du inga egna idéer om hur du ska hantera det här i A3 är bilderna ett bra ställe att börja. I
A3 är statuskoderna exakt specificerade (issue #9): 26 för fel antal spelomgångar, 27 för fel
antal spelare, 28 när korten tar slut och 1 för alla övriga fel.
Veckans övningsuppgifter
Section titled “Veckans övningsuppgifter”Övningsuppgifterna Klassisk digital klocka (“den tidlösa uppgiften”) och Ellips med inkapsling (“den fängslande uppgiften”) ligger på träningsnivå. Fördjupningsuppgiften Alarmerande arv lägger till ett larm på den digitala klockan, med hjälp av arv. Mats påminner i slutet om att inte hoppa över övningsuppgifterna. Har du inte begreppen kring klasser på plats blir det svårt att ta till dig A3:s uppgiftsbeskrivning, och övningarna kan spara tid i slutänden. Mikael tillägger att den digitala klockan inte är särskilt stor när du väl har grunderna i klasser.
A3: inspelningarna och sammanfattningarna
Section titled “A3: inspelningarna och sammanfattningarna”Till A3 hör en inspelning som beskriver kortspelet Tjugoett och de regler som gäller i uppgiften. Det handlar alltså inte om Black Jack. Inspelningen, med Mats och Johan Leitet, publicerades på YouTube 2021 men är inspelad före 2018, och visas därför i grått. Den har nu fått en sammanfattning i text, som också rättar det som har ändrats sedan dess eller som sägs fel. Detsamma gäller inspelningen om A3:s klassdiagram.
Har du inte spelat Tjugoett förut, menar Mikael, är det värt att se videon där de sitter och spelar. Det blir lättare att bygga upp en bild av spelet, och av programmet, i huvudet när du har sett kortlek, kortbord, spelare och spelkort framför dig.
Det som har tillkommit sedan inspelningen är att klassdiagrammet nu är givet. En del av uppgiften är att implementera det till punkt och pricka. Skapa som vanligt din egen kopia av uppgiften via ActionHub.
Att läsa klassdiagrammet
Section titled “Att läsa klassdiagrammet”Klassdiagrammen följer UML-standarden så nära som möjligt, och ser därför lite annorlunda ut än i inspelningen. README-filen förklarar hur de ska läsas, och under varje klass finns ett eget diagram och en text med detaljerna om fält, egenskaper och metoder.
Mats förklarar att diagrammet ger ramarna: fyra klasser, CardTable, Deck, Player och
PlayingCard. Varje ruta har tre fack: klassens namn, egenskaper och fält, och sist metoderna. Ett
+ framför betyder publik och ett - privat. I Player, PlayingCard och Deck är alla metoder
publika. I CardTable är determineWinner(), deal() och playOut() privata, medan konstruktorn
och playRounds() är publika.
Mikael kopplar facken till begreppet API. Det som är publikt är det du kan göra med klassen när du använder den. Det privata är ofta upp till den som skriver klassen, men inte i den här uppgiften, eftersom även de privata delarna är specificerade.
Är klassdiagram något som hjälper när du programmerar? Ovärderligt, säger Mats. I kursen i mjukvarukvalitet i årskurs två undervisar han tillsammans med en spelutvecklare, och de använder samma begrepp men menar olika saker. Då är det skönt att kunna plocka fram en bild som på ett övergripande sätt beskriver vad koden handlar om. Mikael håller med om att diagrammet säger mycket, men inte allt. Därför finns detaljerna längre ner i uppgiftsbeskrivningen. Uppgiften är hårt styrd, eftersom kursen inte handlar om att designa, utan om att skriva kod utifrån en beskrivning. Att få jobba så är inte ovanligt i arbetslivet heller, säger Mats.
I vilken ordning du bygger
Section titled “I vilken ordning du bygger”En vanlig fråga är i vilken ordning klasserna ska göras. Mats svar är nerifrån och upp, efter beroendena, det vill säga pilarna:
PlayingCardär redan färdig. Den ska du inte röra.Deckär påbörjad, menadd(),deal()ochcountåterstår. Börja här, och testkör genom att skapa en kortlek iapp.jsoch se vad du kan göra med den.Player, så att du har en spelare som kan agera i spelet.CardTable, som syr ihop allt när de andra delarna är på plats.
Det går också att arbeta uppifrån och ner, med stubbar för det som inte finns ännu. Mikael är
lockad av det: han skulle börja med utskrifterna under Utfall i README-filen, gå in i app.js,
ta emot argumenten och fundera på vilken metod i CardTable som anropas först. Men en fördel med
att börja i Deck är att det är en väl avgränsad klass som du kan testa med ett litet
huvudprogram innan du går vidare.
Inga givna tester
Section titled “Inga givna tester”Till skillnad från A2 finns det inga enhetstester i A3. Du måste själv testa manuellt, och det talar
ännu mer för att använda app.js till att skapa objekt och kontrollera att t.ex. add(), deal()
och shuffle() i Deck fungerar som de ska. Försöker du pussla ihop allt i CardTable innan
delarna fungerar gör du det onödigt svårt för dig. Hur du har testat ska du också beskriva i
REFLECTION.md.
Den utmanande varianten
Section titled “Den utmanande varianten”Den utmanande varianten är inte obligatorisk, men kan vara en väg mot ett högre betyg. Där finns ett
arv insmuget: pilen med ofylld pilspets mellan Hand och PlayingCardCollection betyder att Hand
ärver från PlayingCardCollection. Du behöver inte välja variant från början. Den utmanande bygger
vidare på den grundläggande, så du kan fortsätta med den när du har gjort klart grunduppgiften.
Inför start rekommenderar Mikael att du ger dig själv kalendertid: uppgiften är minst lika stor som A2, och det tar några dagar att läsa, sova på det, läsa igen och hitta en startpunkt. Att träna på att ta till sig stora mängder text och bygga upp en bild av den är i sig en bra övning. Deadline är tisdag 3 november kl. 17.00, ungefär tre veckor bort, med muntlig examination 5–6 november. Du som har lämnat in A2 kan börja direkt, du har i princip all kunskap som behövs.
Läget med A2
Section titled “Läget med A2”A2 hade deadline i fredags och drygt 40 har lämnat in. Mats och Mikael rättar för fullt och har tre veckor på sig. Du kan få återkoppling på din merge request innan resultatet syns i Ladok, som blir det först när Mats har attesterat det. Det ska inte behöva dröja mer än 3–4 dagar efter återkopplingen.
Mikaels intryck så här långt är att i princip alla använder AI i någon form. Koden är mest den
välbekanta studentkoden, med de konstruktioner kursen har gått igenom, ibland lite stökig och ibland
finslipad och komprimerad. Någon lösning med reduce var den mest komprimerade han hade sett.
Ett hål i testerna: glesa arrayer
Section titled “Ett hål i testerna: glesa arrayer”Mats har upptäckt en sak som A2:s tester missar. En array kan ha ett hål, en position utan något värde alls:
const numbers = [1, 2, , 4, 5]console.log(numbers.length) // 5Iteratormetoder som every() och some() hoppar helt sonika över det tomma elementet. En
validering av att arrayen bara innehåller tal går därför igenom, trots att position 2 saknar värde:
numbers.every((n) => typeof n === 'number') // trueEn for...of-sats besöker däremot varje position, och ger undefined för hålet. Vill du validera
på ett felsäkert sätt är den därför ett bättre val än every() eller some(), även om de är
trevliga:
for (const n of numbers) { if (typeof n !== 'number') { throw new TypeError('The array must contain only numbers.') }}Testerna åtgärdas till nästa år.
Frågor från chatten
Section titled “Frågor från chatten”När lär vi oss PHP eller Python? Det finns inga kurser i PHP eller Python inom programmet. Däremot finns kurser där du själv väljer språk, bland annat projektkurser.
Hur kommer jag igång med Angular och React? Du gör det redan, säger Mats. Båda bygger på JavaScript, och kursen lär dig grunderna, men det finns inga kurser i specifika ramverk inom programmet. Det är ett medvetet val: undervisningen fokuserar på de generella delarna. Redan nästa vecka lämnar kursen till viss del Node.js för webbläsaren, ett första steg mot de webbkomponenter som kommer i kursvecka 10, ett standardiserat sätt att skapa komponenter. I serverprogrammeringen används också ett ramverk, Express.
Mats beskriver hur programmet hänger ihop. Under läsperiod 2 läser du den här kursen (1DV025) och kursen i serverprogrammering (1DV026). I läsperiod 3 kommer en kurs i objektorientering, med Java som verktyg, medan 1DV026 fortsätter. I läsperiod 4 kommer projektkursen 1DV613, där du själv väljer hur du bygger en fullstackapplikation, från klient till server, med vilket språk och ramverk du vill.
Finns det valbara kurser i React eller Electron? Inte vad Mats känner till, men inget hindrar dig från att läsa en kurs vid ett annat lärosäte. Mikael skulle hellre bygga något eget, och Mats tipsar om att lägga ut dina projekt publikt på GitHub som en portfölj att länka till när du söker jobb. I de kurser i årskurs två och tre där Mikael låter studenterna arbeta med t.ex. React eller Electron är de så förberedda att de lär sig tekniken inom ramen för kursen. Och Electron har du redan använt: Visual Studio Code är byggt med det, ett sätt att skriva skrivbordsapplikationer med HTML, CSS och JavaScript.
Grunder, ramverk och AI
Section titled “Grunder, ramverk och AI”Mikael ser kursens uppgift som att ge dig gedigna grunder. Under en karriär kommer och går många ramverk, och med grunderna på plats kan du snabbt lära dig nästa. Mats håller med, men är osäker på hur det ser ut idag, med AI som redan påverkar alla. Han tror fortfarande att det är de grundläggande koncepten som är viktiga, eftersom detaljerna kan du ta fram på egen hand eller med en assistent, men inte den övergripande förståelsen.
Han tar kursens egen webbplats som exempel. Den byggde på Gatsby, som i princip har slutat utvecklas. På Mikaels rekommendation har den nu portats till Astro. På egen hand hade det tagit mansmånader, men med AI som hjälp och en mängd förkunskaper tog det två och en halv vecka. Om erfarenheten är allmängiltig vet han inte. Mikael tror att ramverkens roll minskar när allt mer kod skrivs med kodagenter, och att det blir lättare att byta mellan dem, även om platsannonserna fortfarande efterfrågar React.
Att våga fråga
Section titled “Att våga fråga”Någon tyckte att det är svårt att ställa frågor under passen, när Mats och Mikael pratar hela tiden. Då finns två andra vägar: den individuella handledningen, där du får full uppmärksamhet, och att formulera en fråga i lugn och ro i Zulip. För individuell handledning skriver du ditt namn i Zulip-ämnet HT26 individuell handledning enligt anvisningarna där, så blir du kontaktad av Mats eller Mikael.
Mikael ser det också som en träningssak. I många arbetsmiljöer behöver du våga fråga om det du inte vet, oavsett grupp, och fortsätta fråga tills du har fått svar, även om svaret inte alltid kommer lika vänligt som du hade hoppats.
Mats förklarar varför frågorna behövs. Materialet passar kanske 80–90 procent av studenterna, men för att nå de sista procenten skulle det behöva vara tio gånger så stort. Lärarna har fler förklaringsmodeller än de som står i materialet. Ett av de roligaste momenten är när de nio, tio, femton modellerna tar slut och de måste hitta på en ny, och sedan ser poletten trilla ner. Så fråga på.
Inför torsdagen
Section titled “Inför torsdagen”En student påminner om att det är dags att söka vårens kurser. Information har skickats ut. Kursen i serverprogrammering, 1DV026, går över två läsperioder och behöver inte sökas igen, men du måste registrera dig på nytt när den fortsätter.
Mikael hade förberett ett exempel om arv, men det sparas till torsdagen, tillsammans med kodexempel och kanske veckans utmaning, som är lite annorlunda och nästan lite svårare den här veckan. Fram till dess kan du titta på presentationerna och inspelningarna om arv. För dig som har lämnat in A2 gissar Mikael att veckans fokus blir att komma igång med A3.
Torsdag med arv
Section titled “Torsdag med arv”Torsdagen i kursvecka 7 handlade om veckans tema, arv. Mikael gick igenom ett klassiskt exempel med
djur: extends, att överskugga metoder, super och varför privata fält inte går att nå från en
härledd klass. Mats presenterade sedan exemplet Varde ljus, med en lampa som kan dimmas, och
visade hur du stegar igenom koden i debuggern.
Om passet
Section titled “Om passet”Torsdagens pass i kursvecka 7 hölls av Mats och Mikael tillsammans och handlade om veckans tema, arv. Mikael gick igenom ett klassiskt exempel med djur, och Mats presenterade ett nytt exempel, Varde ljus, med en lampa som kan dimmas, och visade hur du stegar igenom koden i debuggern i Visual Studio Code. Inspelningen börjar när Mikael tar fram sitt exempel.
Ett klassiskt exempel: djur
Section titled “Ett klassiskt exempel: djur”Mikaels första exempel är, som han själv säger, “jätteklassiskt”, eftersom den typen av exempel används i väldigt många kurser i objektorienterad programmering. Ibland hjälper det att tänka på fysiska saker, eller påhittade saker med någon verklighet bakom, när du ska tänka objektorienterat. Här ska en hund och en katt representeras, och båda ska kunna låta: hunden skäller och katten mjauar.
Vill du placera dem i en arvshierarki letar du efter det som är gemensamt för de två djuren och
lyfter ut det till en egen klass, här Animal. I den här världen har varje djur ett namn, som
skickas med till konstruktorn och sparas i ett privat fält. Utifrån behöver namnet gå att läsa,
och därför finns en “getter”. Dessutom finns ett generellt ljud, och toString() anpassar hur
objektet beskriver sig som sträng. Mikaels kod är inte publicerad, så kodexemplen i det här avsnittet
är rekonstruerade utifrån genomgången, men ungefär så här såg det ut:
export default class Animal { #name
constructor(name) { this.#name = name }
get name() { return this.#name }
speak() { return `${this.#name} makes a sound.` }
toString() { return `This is the animal ${this.#name}.` }}Mats konstaterar att den lilla klassen innehåller de vanliga byggstenarna i en klass. Mikael påpekar att det inte finns några spår av arv i den, det är en helt vanlig klass. Det vet du inte alltid när du skapar en klass, säger Mats: ibland visar det sig först senare att den behöver utökas, och då är arv en väg att gå.
extends: utöka eller specialisera?
Section titled “extends: utöka eller specialisera?”Katten ärver från djuret med extends:
import Animal from './Animal.js'
export default class Cat extends Animal { speak() { return `${this.name} meows.` }}Mikaels egen bild är att det nästan är som att kopiera in alla delar av Animal i Cat och sedan
bygga vidare, även om han medger att bilden är lite slarvig och att det egentligen är mer “trådar
som kopplas ihop”. Mats poängterar att alla bygger sina egna metaforer. I slutet av kursen har du
förhoppningsvis en egen bild, så att du förstår class Cat extends Animal direkt utan att behöva
läsa koden ord för ord.
Men vänta, säger Mats och spelar nybörjare: extends betyder utöka, men här har ju ingenting
lagts till? speak() finns redan i Animal. Mikaels svar är att arv inte bara handlar om att
utöka, utan också om att specialisera. Animal har ett grundläggande beteende som inte passar
katten, så katten anpassar det. Det kallas att överskugga (override) en metod. Att anropa speak()
på ett Cat-objekt kör kattens version. Du kan förstås också lägga till något som är unikt för
katten, t.ex. en metod för att spinna. Då utökar du i stället för att förändra.
Hunden: egen konstruktor och super
Section titled “Hunden: egen konstruktor och super”Klassen Dog är större. Här har hunden också en ras, och därför behövs en egen konstruktor. Katten
hade ingen, och då används konstruktorn i Animal.
import Animal from './Animal.js'
export default class Dog extends Animal { #breed
constructor(name, breed) { super(name) this.#breed = breed }
get breed() { return this.#breed }
speak() { return `${this.name} barks.` }
toString() { return `${super.toString()} It is a ${this.#breed}.` }}När en härledd klass har en egen konstruktor måste den anropa konstruktorn i föräldraklassen med
super(), här med namnet som Animal tar hand om. Mats lägger till att anropet måste komma före
första användningen av this. Byter du plats på raderna säger JavaScript ifrån med ett
ReferenceError.
super dyker också upp i toString(), men som super.toString(). Det är inte ett anrop till
konstruktorn, utan till föräldraklassens metod. På så sätt kan den härledda klassen återanvända
koden i föräldraklassen och bygga vidare på den. Utan super. hade toString() anropat sig själv,
vilket “sällan är en bra idé”, som Mats uttrycker det.
Mikael ser arvet som ett sätt att återanvända kod: Cat och Dog delar en gemensam kodbas i
stället för att båda innehålla samma kod. Det går också att göra tvärtom: skriv en kattklass och en
hundklass, luta dig tillbaka och se vad som är lika. Tanken på DRY (don’t repeat yourself) kan
då leda dig till att bygga upp en arvshierarki i efterhand.
Privata fält ärvs inte åtkomligt
Section titled “Privata fält ärvs inte åtkomligt”Mats pekar på en detalj: i Dog står det this.name, inte this.#name. Varför kan inte Dog
använda det privata fältet, när den ändå utökar Animal? För att privata medlemmar bara syns inne
i den klass där de deklareras. Varken den som använder Animal eller de klasser som ärver från den
kommer åt dem. this.name går i stället via den publika “gettern”, som Dog har ärvt.
Mikael brukar göra allt så privat som möjligt i sina klasser och sedan öppna upp där det behövs, det är det inkapsling handlar om. “Gettern” är det som öppnar upp för läsning, både för huvudprogrammet och för de härledda klasserna.
Huvudprogrammet: instanceof och vem som bestämmer
Section titled “Huvudprogrammet: instanceof och vem som bestämmer”I huvudprogrammet skapas ett generellt djur, en hund och en katt med new, och läggs i en array:
const animals = [new Animal('Djuret'), new Dog('Bruno', 'labrador'), new Cat('Sune')]
for (const animal of animals) { console.log(animal.speak())}
console.log(animals[1] instanceof Dog) // trueconsole.log(animals[1] instanceof Animal) // trueSka det ens gå att skapa ett objekt av Animal? Här går det, men Mats nämner att du kan hindra
det, även i JavaScript, och göra klassen abstrakt: det går att ärva från den, men inte att skapa
objekt av den. Mikael väljer att inte gå ner i det hålet.
Behöver du veta om en referens pekar på en hund? Ibland, säger Mats. Med instanceof kan du
undersöka vilken typ ett objekt har när koden körs. En hund är en Dog, men den är också en
Animal, så båda raderna ovan ger true. Frågar du om en bil är ett djur blir svaret false
(“och snälla, prata inte om bilar av märket Jaguar”). Det är inget du skriver särskilt ofta, men
möjligheten finns.
Mats vill också lyfta loopen. Vilken speak() körs, den i Animal, Dog eller Cat? Första
gången kan det verka magiskt. Svaret är att det är objektet som kör koden som bestämmer. Mats
beskriver referensen som ett koppel: kopplet gör ingenting själv, men rycker du i det händer det
saker med hunden, katten eller djuret som sitter i andra änden.
Varde ljus: lampa och dimbar lampa
Section titled “Varde ljus: lampa och dimbar lampa”Mats har tagit fram ett nytt exempel,
example-let-it-be-light.
Du behöver vara inloggad på GitLab för att se det. Exemplet visar arv och polymorfism, “ett svårt
ord för något som egentligen är oerhört enkelt”. Mikaels speak() är ett exempel på det.
Exemplet har två klasser. Light är en lampa som kan tändas och släckas. DimmableLight ärver den
förmågan, eftersom det vore dumt att inte utnyttja den, och lägger till en ljusstyrka. Klassdiagrammet
i README-filen visar arvet med pilen Light <|-- DimmableLight.
Lighthar det privata fältet-isOnoch de publika metodernaon(),off()ochtoggle(), som byter mellan tänd och släckt, samttoString().- Diagrammet är kodnära, inte rent konceptuell UML. Därför finns också
+isOn : boolean {readOnly}, som ska läsas som en “getter” utan motsvarande “setter”. DimmableLighthar det privata fältet-brightnessoch den publika egenskapen+brightness, med både “getter” och “setter”, plusdim(),brighten()och en egentoString()som överskuggar föräldraklassens.
README-filen förklarar hur diagrammet ska läsas och att samma sak kan ritas på flera sätt, beroende
på språk och verktyg. Där finns också en ordlista. Mikael använder många olika begrepp när han
pratar, eftersom han arbetar med objektorientering i många språk där samma sak heter olika saker.
Mats utgår från de begrepp som används på MDN: arv (inheritance), ärva från eller utöka
(inherit from, extend), föräldraklass (parent class) och överskugga (override). Basklass
(base class) betyder på MDN en klass utan extends, även om Mats medger att han själv säger
basklass om allt. En sak som finns med i onödan, säger Mats, är protected: JavaScript har bara
privat och publikt.
Mats har också valt att döpa filerna med gemener, light.js och dimmable-light.js. Själv brukar
han döpa filen efter klassen, Light.js. Det finns olika standarder, och det viktiga är att du är
konsekvent.
Validering på ett ställe
Section titled “Validering på ett ställe”Konstruktorn i DimmableLight börjar med super(). Här finns ingen information att skicka vidare
till Light, säger Mats, men han har med anropet ändå.
Sedan tilldelas this.brightness, inte this.#brightness. Varje ändring passerar därmed
“settern”, som är första gången kursen visar en sådan. Den kontrollerar att värdet är ett heltal i
det slutna intervallet 1–100. Noll är inte med, för då kan du lika gärna släcka lampan.
set brightness(value) { if (!Number.isInteger(value) || value < 1 || value > 100) { throw new RangeError('Brightness must be an integer between 1 and 100') } this.#brightness = value}
dim(amount = 10) { this.brightness -= amount}Även dim() och brighten() går via “settern”, så regeln finns på ett enda ställe i koden.
Ska varje privat fält ha en “getter” och en “setter”? Nej, svarar Mats. Ibland är ett fält en intern angelägenhet för klassen. Här vill han kunna fråga lampan hur starkt den lyser, och därför behövs en “getter”. Fältet är privat för att det bara ska kunna få ett heltal mellan 1 och 100, inte en sträng, ett datum eller en referens till något annat objekt, och därför behövs en “setter” som kontrollerar värdet innan det sätts.
toString() i DimmableLight använder super.toString(): är lampan släckt räcker föräldraklassens
beskrivning, annars läggs en stapel och ljusstyrkan till. Mikael konstaterar att exemplet täcker
allt från djurexemplet och lite till, med relativt lite kod. Det kan vara bra att ha som referens
när du skriver egen kod, och som träning i att läsa ett klassdiagram och sedan se hur det hänger
ihop med koden.
Debuggern
Section titled “Debuggern”Mikael erkänner att hans standardläge vid felsökning är console.log, och det fungerar bra långt.
Men att kunna använda en debugger är värdefullt: du kan läsa av variabler, följa flödet och få en
känsla för hur koden exekveras. Mats, som gärna har bilder i huvudet, använder debuggern väldigt
ofta. Har du svårt att föreställa dig hur din kod körs, i vilken ordning, hur värden ändras och när
objekt skapas, kan debuggern ge dig en visuell återkoppling på just det.
Mats visar hur det går till i app.js:
- Klicka i marginalen till vänster om ett radnummer. En röd punkt markerar en brytpunkt (breakpoint).
- Tryck F5, eller välj Run → Start Debugging, och välj Node.js. Koden körs fram till brytpunkten och stannar.
- Använd verktygsfältet för att stega: Step Over kör satsen utan att gå in i den, Step Into går in i metoden som anropas och Step Out lämnar den. Du kan när som helst stoppa eller starta om.
Mats stegar in i on() och ser i variabelvyn att this är objektet som kör koden och att #isOn
ändras från false till true. Visual Studio Code markerar det värde som den senaste satsen
ändrade. Vid utskriften anropas toString(), och returvärdet Light is on syns bland variablerna
innan det dyker upp i debugkonsolen.
Mikael kunde själv inte köra debuggern under passet, eftersom han kör i WSL och den startade i PowerShell. Det har han att fixa efteråt. I A3 går det bra att felsöka med utskrifter, men debuggern kan ge dig en bättre förståelse för hur delarna hänger ihop, och det är bra träning.
Inför nästa vecka
Section titled “Inför nästa vecka”Med exemplet att forka och studera, och för många fokus på att ta sig framåt i A3, avslutas veckan. Nästa vecka lämnar kursen Node.js för webbläsaren.
Stödjande inspelningar
Section titled “Stödjande inspelningar”Inkapsling
Section titled “Inkapsling”Ska alla egenskaper på ett instansierat objekt alltid vara tillgängliga utanför klassen?
PresentationProblemet: oskyddade egenskaper
Section titled “Problemet: oskyddade egenskaper”En tärningsklass med två egenskaper (faceValue, maxFaceValue) fungerar bra i normalfallet, men inget hindrar att egenskaperna sätts till vilket värde som helst utifrån:
export class Die { constructor (maxFaceValue = 6) { this.maxFaceValue = maxFaceValue this.roll() }
roll () { this.faceValue = Math.floor(Math.random() * this.maxFaceValue) + 1 }}
const die = new Die()die.faceValue = 'This is a strange value for a die!' // perfectly allowed, but wrongdie.maxFaceValue = 'twentyish'die.roll() // the result is NaNGår det att dölja eller skydda en del av ett objekts data för omvärlden? Ja.
Ett fält är egentligen bara en egenskap, men deklarerad ovanför konstruktorn i klassdefinitionen istället för att skapas dynamiskt inuti den. Fördelen är att alla en klass egenskaper syns samlade på ett ställe:
class Die { maxFaceValue // a field
constructor (maxFaceValue = 6) { this.maxFaceValue = maxFaceValue this.roll() }}Ett fält kan vara publikt eller privat – att det är ett fält handlar bara om var egenskapen deklareras, inte om den är skyddad.
Semiprivata egenskaper (den gamla konventionen)
Section titled “Semiprivata egenskaper (den gamla konventionen)”Innan JavaScript hade riktiga privata fält användes en namnkonvention: ett inledande understreck (_age) signalerade “rör inte den här utifrån” – men skyddade den inte på något sätt rent tekniskt. En sådan semiprivat egenskap går fortfarande att läsa och skriva till precis som vilken annan egenskap som helst. Konventionen lever kvar i mycket äldre kod, men är på väg att fasas ut till förmån för riktiga privata fält.
Privata fält
Section titled “Privata fält”Ett privat fält skapas med ett inledande nummertecken (#) och kan bara nås inifrån klassdefinitionen – ett genuint språkskydd, inte bara en konvention:
class Die { #faceValue #maxFaceValue
constructor (maxFaceValue = 6) { this.maxFaceValue = maxFaceValue this.roll() }}Ett privat fält måste deklareras (skrivas ut som fält i klassdefinitionen) innan det kan användas – till skillnad från vanliga egenskaper kan de inte skapas dynamiskt “i farten”. Försöker kod utanför klassen komma åt ett privat fält blir det ett tydligt fel direkt, istället för att bara ge undefined.
Getters och setters
Section titled “Getters och setters”Ett privat fält är helt otillgängligt utifrån om inget annat görs – även för att bara läsa av det. Lösningen är accessor- och mutatormetoder: en getter (get) binder en egenskap till en funktion som anropas när värdet hämtas, en setter (set) binder egenskapen till en funktion som anropas när värdet sätts:
class Dog { #age
constructor (name, age) { this.name = name this.age = age // calls the setter }
get age () { return this.#age }
set age (value) { if (!Number.isInteger(value) || value < 1) { throw new Error('Invalid age.') } this.#age = value }}En getter och setter anropas som om de vore en vanlig egenskap – myDog.age, inte myDog.age() – men implementationsmässigt är de funktioner. En getter tar aldrig emot argument (inget att skicka med vid hämtning); en setter tar alltid emot exakt ett argument (värdet som ska sättas) och kan innehålla godtycklig valideringslogik, inklusive att kasta ett undantag om värdet inte duger.
Har en klass bara en getter (ingen setter) för ett fält blir egenskapen skrivskyddad (“read-only”) – ett försök att tilldela den ett nytt värde ger ett tydligt fel istället för att tyst misslyckas.
Tärningen, tredje försöket
Section titled “Tärningen, tredje försöket”Med privata fält och getters/setters kan tärningsklassen skyddas fullt ut:
export class Die { #faceValue #maxFaceValue
constructor (maxFaceValue = 6) { this.maxFaceValue = maxFaceValue this.roll() }
get faceValue () { return this.#faceValue }
get maxFaceValue () { return this.#maxFaceValue }
set maxFaceValue (value) { if (!Number.isInteger(value) || value < 1) { throw new Error('Invalid number of faces.') } this.#maxFaceValue = value }
roll () { this.#faceValue = Math.floor(Math.random() * this.#maxFaceValue) + 1 }}faceValue har bara en getter – kan bara läsas, aldrig sättas direkt (bara via roll()). maxFaceValue har både getter och setter, men settern validerar värdet innan det accepteras. En getter/setter-kombination som bara returnerar respektive tilldelar fältets värde rakt av, utan någon egen logik, ger däremot inget skydd alls – då är det lika bra att ha en publik egenskap.
En viktig varning: referenstyper i privata fält
Section titled “En viktig varning: referenstyper i privata fält”Mönstret ovan fungerar problemfritt så länge det privata fältet är en värdetyp (som number). Är fältet istället en referenstyp (till exempel en array) räcker det inte att bara returnera fältet direkt i en getter – då lämnas referensen ut, och den som anropar kan muterar det “privata” innehållet ändå (en så kallad “privacy leak”). I det fallet behöver gettern istället returnera en kopia av objektet, annars är inkapslingen meningslös.
Beräknade egenskaper
Section titled “Beräknade egenskaper”Ibland finns det ingen underliggande fält alls bakom en getter – värdet som returneras kan istället vara beräknat från flera andra fält (till exempel en area beräknad från bredd och höjd). Det finns ingen anledning att lagra ett sådant värde separat när det ändå kan räknas fram vid behov.
Återanvändning av kod kan ske på många olika sätt. Här behandlas mekanismen “Arv”.
PresentationUpprepning av kod
Section titled “Upprepning av kod”Två klasser, Person och Student, kan hamna med exakt samma kod – samma egenskaper (name, age) och samma toString-metod, plus en eller två extra delar i Student. Behöver det vara så? Nej – med hjälp av arv (“inheritance”) kan kod återanvändas istället för att upprepas.
Återanvändning av kod genom arv
Section titled “Återanvändning av kod genom arv”Genom att låta Student ärva från Person med extends slipper Student duplicera det som redan finns i Person:
export class Person { constructor (name, age) { this.name = name this.age = age }
toString () { return `Jag, ${this.name}, är ${this.age} år.` }}
// student.jsimport { Person } from './person.js'
export class Student extends Person { constructor (name, age, onCampus) { super(name, age) this.onCampus = onCampus }
toString () { const form = this.onCampus ? 'campus' : 'distans' return `Jag, ${this.name}, läser på ${form}, och är ${this.age} år.` }}Person sägs vara basklass till Student, och Student utökar basklassen. Tumregeln för när arv passar: en Student är en Person (aldrig tvärtom) – men en Student är till exempel inte en hund, även om en hypotetisk Dog-klass också råkade ha name och age. Det gemensamma måste vara en genuin “är en”-relation, inte bara råkat ha samma egenskaper.
super(name, age) i konstruktorn anropar basklassens konstruktor för att initiera det den redan vet hur den ska initiera – annars måste Student duplicera det arbetet. super måste anropas innan this används i en konstruktor som ärver, och görs det så gott som alltid när arv används. Students egen toString överskuggar (“overrides”) den som ärvts från Person – anropas toString på ett Student-objekt är det alltid den mest specifika versionen (närmast i kedjan) som körs. Utelämnas den egna toString helt används istället Persons version.
Klassdiagram
Section titled “Klassdiagram”Ett klassdiagram (enligt standarden UML) är ett sätt att beskriva klasser och deras relationer utan att behöva rita alla de faktiska prototypreferenserna. Det består av tre fält: klassens namn överst, dess egenskaper i mitten, och dess metoder längst ner. En pil mellan två klasser visar arv – att den ena ärver från (är en) den andra.
Arv under huven
Section titled “Arv under huven”Bakom kulisserna byggs faktiskt två separata prototypkedjor upp när Student ärver från Person: en instansprototypkedja (en instans → Student.prototype → Person.prototype → Object.prototype) och en klassprototypkedja (Student → Person → Function.prototype, eftersom klasser i grunden är funktioner). Det är den förstnämnda som är intressant i praktiken. Klassdiagrammet ovan säger däremot ingenting om detta – det är en enklare, mer lättarbetad representation, men det är viktigt att komma ihåg att JavaScript gör betydligt mer under huven än vad diagrammet visar.
instanceof
Section titled “instanceof”Operatorn instanceof avgör om ett värde är en instans av en given klass, genom att följa just prototypkedjan:
const person = new Person('Nisse', 42)const student = new Student('Ellen', 23, true)
console.log(person instanceof Person) // OUTPUT: trueconsole.log(person instanceof Student) // OUTPUT: false – Nisse is not a studentconsole.log(student instanceof Person) // OUTPUT: true – a student is a personconsole.log(student instanceof Student) // OUTPUT: trueinstanceof blir särskilt användbart längre fram i kursen vid felhantering: när ett undantag fångas i ett catch-block kan man fråga felobjektet vilken specifik feltyp det är, och hantera olika typer av fel på olika sätt.
Validering och strukturerad felhantering
Section titled “Validering och strukturerad felhantering”Att validera exempelvis strängar, kontrollera att de är skrivna på ett visst format, är mycket vanligt. Denna inspelning tittar på reguljära uttryck och ser om de kan hjälpa (eller stjälpa?) dig.
PresentationValidera en egenskaps värde
Section titled “Validera en egenskaps värde”Att kontrollera att en egenskap är korrekt formaterad – till exempel att ett svenskt postnummer består av fem siffror, eventuellt med ett mellanslag mellan tredje och fjärde siffran – kan göras med en handskriven kontroll i en setter, tecken för tecken:
set postalCode (value) { let valid = false if (value.length === 5 || (value.length === 6 && value.indexOf(' ') === 3)) { let i = 0 for (; i < value.length; i++) { if (value[i] === ' ') continue if (value[i] < '0' || value[i] > '9' || (i === 0 && value[i] === '0')) break } valid = i >= 5 && i <= 6 } if (!valid) { throw new Error('Invalid postal code.') } this._postalCode = value}Det fungerar, men blir snabbt både svårt att skriva korrekt och tungt att underhålla – och en enda ny regel (t.ex. att även sexsiffriga postnummer utan mellanslag ska godkännas) kräver att man river upp hela logiken igen.
Reguljära uttryck till undsättning
Section titled “Reguljära uttryck till undsättning”Ett reguljärt uttryck är ett mönster som beskriver kombinationer av tecken i en sträng, skrivet mellan snedstreck (/mönster/). Samma valideringslogik som ovan kan uttryckas i en enda rad:
set postalCode (value) { if (!/^[1-9]\d{2} ?\d{2}$/.test(value)) { throw new Error('Invalid postal code.') } this._postalCode = value}Uttrycket /^[1-9]\d{2} ?\d{2}$/ läses del för del: ^ markerar start av strängen, [1-9] exakt en siffra 1–9 (inte 0, eftersom postnummer aldrig börjar på 0), \d{2} exakt två siffror, ⎵? noll eller ett mellanslag, \d{2} ytterligare två siffror, och $ slutet av strängen. Ska även sexsiffriga postnummer (utan krav på mellanslag) godkännas räcker det att byta ut det sista \d{2} mot \d{2,3} – bara den delen behöver ändras.
Reguljära uttryck finns i de flesta programmeringsspråk (Python, Java, C# m.fl.), och det finns gott om webbaserade verktyg (till exempel regex101 och ExtendsClass Regex Tester) för att testa och förstå ett uttryck interaktivt innan det klistras in i koden. Mer finns att läsa på MDN under “Regular expressions”.
Kommandoradargument
Section titled “Kommandoradargument”En applikation kan göras mer generell genom att ta emot kommandoradargument när den startar (node src/app.js ett två 3 4). Node gör listan med argument – argumentvektorn – tillgänglig som en array via process.argv. De två första elementen (sökvägen till node.exe och sökvägen till applikationsfilen) är oftast ointressanta och hoppas över med Array#slice:
const add = function (a, b) { return a + b}
const myArgs = process.argv.slice(2)const sum = add(Number(myArgs[0]), Number(myArgs[1]))Ett alternativt, kortare sätt att plocka ut de två första relevanta argumenten och samtidigt konvertera dem till tal:
const [first, second] = process.argv.slice(2).map(Number)const sum = add(first, second)Avsluta en Node-applikation
Section titled “Avsluta en Node-applikation”Går allt bra sätter Node automatiskt applikationens avslutningskod till 0 när den avslutas. Kastas ett undantag som inte hanteras avslutas applikationen istället med avslutningskod 1. Det går att bestämma en egen avslutningskod innan applikationen avslutas – vanligtvis genom att sätta process.exitCode till ett värde och låta Node avsluta applikationen på ett kontrollerat sätt (att tvinga fram ett direkt avslut med process.exit() är oftast onödigt och bör undvikas):
try { main()} catch (err) { console.error(err.message) process.exitCode = 1
if (err instanceof MyError) { process.exitCode = 123 }}Genom att kontrollera vilken feltyp som fångades (med instanceof, se förra veckans genomgång av arv) kan olika fel resultera i olika avslutningskoder – något som blir relevant längre fram när automatiska tester ska tolka om en körning lyckades eller misslyckades.
Läsanvisningar
Section titled “Läsanvisningar”Under läsanvisningar hittar du veckans kapitel som ska läsas i kurslitteraturen. Planera upp och dela upp läsningen under veckan.
Övningar
Section titled “Övningar”Under Övningsuppgifter hittar du övningsuppgifter som det är lämpligt att du arbetar med under veckan.
Seminarier + individuell handledning
Section titled “Seminarier + individuell handledning”Examinationsuppgift A3
Section titled “Examinationsuppgift A3”Efter denna veckas innehåll är du fullt förberedd för att genomföra examinationsuppgift A3. Ett bra mål för denna vecka är att klona ner uppgiften och börja bekanta dig med den.
Om inspelningen
Section titled “Om inspelningen”Inspelningen är från innan 2018, men publicerades på YouTube 2021. Mats och Johan Leitet spelar Tjugoett med en riktig kortlek vid ett bord och pratar under tiden om hur spelet kan “objektifieras”: vilka objekt som finns, vad de ska kunna göra och vem som ansvarar för vad. Johan är giv, Mats spelare. Två högar på bordet får föreställa draghögen och slänghögen.
Sedan inspelningen gjordes har uppgiften fått ett färdigt klassdiagram. Mycket av det Mats och Johan resonerar sig fram till syns direkt i det, och sammanfattningen pekar ut var.
En kortlek som blandar sig själv
Section titled “En kortlek som blandar sig själv”Kortleken är färsk från förpackningen och helt oblandad. I verkligheten tar given kortleken och blandar den. I Johans Harry Potter-värld räcker det att peka på kortleken, så blandar den sig själv.
Vilket sätt ska du välja när du programmerar? Det som är enklast att förstå och enklast att
programmera, säger Mats. Att avbilda verkligheten exakt blir ofta för komplext. En kortlek som kan
blanda sig själv gör det enklare i nästa steg: given behöver inte hålla reda på hur blandningen går
till. I klassdiagrammet är det just så: Deck har metoden shuffle().
På samma sätt får spelaren ett kort, snarare än tar det. Varifrån kortet kommer spelar ingen roll
för spelaren, den ska bara kunna ta emot det. I klassdiagrammet har Player ingen referens till
kortleken alls, bara metoden addToHand(). Det är spelbordet, CardTable, som med sin privata
metod deal() hämtar nästa kort från kortleken och lämnar det till en spelare.
En spelomgång
Section titled “En spelomgång”Mats och Johan spelar några rundor och går igenom reglerna på vägen:
- Alla spelare får först ett kort var. Given tar inget kort själv.
- Given spelar sedan mot en spelare i taget. Spelaren får ett kort till. Efter två kort kan spelaren redan ha vunnit (21) eller spruckit (över 21).
- Annars bestämmer spelaren om den vill ha fler kort eller är nöjd. En spelare måste alltid ta två kort, även med ett mycket försiktigt stoppvillkor som 8.
- Är spelaren nöjd drar given kort tills den är nöjd. Given ser inte spelarens hand, och spelaren behöver inte se givens kort.
- Den högsta summan vinner. Efter delomgången lägger både spelare och giv sina kort i slänghögen.
Har spelaren vunnit eller spruckit direkt behöver given inte dra några kort alls i den delomgången.
Ingen människa styr spelet i applikationen. Varken spelare eller giv ser motståndarens hand, så båda behöver en inbyggd regel för när de är nöjda. Johan väljer att given stannar på 17. En våghalsig giv kan stanna på 18, en fegare på 16 eller till och med 15. Spelarna kan ha andra gränser än given.
Klassdiagrammet löser det här enkelt: given är också ett Player-objekt, precis som spelarna. Både
spelare och giv har alltså ett eget stoppvillkor, och den härledda egenskapen canHit svarar på om
de vill ha ett kort till. Den bygger bara på den egna handen och det egna stoppvillkoret, aldrig på
motståndarens hand.
Det är inte Black Jack
Section titled “Det är inte Black Jack”Två saker skiljer Tjugoett från Black Jack, och båda får Mats och Johan att räkna fel under inspelningen:
- Klädda kort är inte värda 10. En knekt är värd 11, en dam 12 och en kung 13. Johan drar en dam och en sexa, och det blir 18, inte 16.
- Given spelar mot en spelare i taget, inte mot alla på en gång. Med flera spelare vid bordet
spelar given en hel delomgång mot den första spelaren, lägger sina kort i slänghögen och spelar
sedan en ny delomgång mot nästa spelare. I klassdiagrammet motsvaras en sådan delomgång av
CardTables privata metodplayOut(dealer, player), och jämförelsen efteråt avdetermineWinner(dealer, player). Båda tar emot given och en spelare.
Esset: 1 eller 14
Section titled “Esset: 1 eller 14”Esset är värt 1 eller 14, det som är mest fördelaktigt för handen. Mats får först ett ess och sedan en åtta. 14 + 8 blir 22, och då har han spruckit. Räknas esset i stället som 1 blir summan 9, och han kan fortsätta dra.
Esset “byter” inte värde en gång för alla. Du skriver inte 1 eller 14 på kortet, som Mats uttrycker
det. Värdet räknas om varje gång handen värderas, utifrån de kort som just då finns på handen. I
startkoden har esset värdet 1 i PlayingCard, och kortet går inte att ändra eftersom objektet är
fryst. Att esset kan räknas som 14 är alltså handens sak, det vill säga Players valueOf(), inte
kortets.
Fem kort under 21
Section titled “Fem kort under 21”En specialregel: får spelaren fem kort på handen och summan fortfarande är under 21 vinner
spelaren direkt, en naturlig vinst. Det är därför handen i klassdiagrammet har multipliciteten
0..5: fler än fem kort kan en hand aldrig ha. Om spelaren vunnit direkt, med 21 eller fem kort,
svarar den härledda egenskapen isNaturalWinner.
När draghögen tar slut
Section titled “När draghögen tar slut”Under inspelningen tar draghögen slut. Det understa kortet delas aldrig ut, eftersom någon kan ha
sett det när högen lades ner. När det bara finns ett kort kvar i draghögen tar given i stället det
kortet och alla kort i slänghögen, blandar om dem och använder dem som ny draghög. I
klassdiagrammet görs det med Decks metoder add(), som tar emot en array med kort, och
shuffle(). Slänghögen har multipliciteten 0..51: eftersom det understa kortet aldrig delas ut
kan aldrig alla 52 kort hamna där.
Det viktiga, betonar Mats, är att du inte tar fram en ny kortlek när draghögen är slut. Då finns plötsligt 52 kort till i spel, och kort som redan ligger på bordet finns i dubbletter. Det är också ett uttryckligt krav i issue #8: under en spelomgång används en kortlek.
Johan påpekar att korten är objekt, alltså referenstyper. Även om det är lätt att kopiera kort i en
dator ska du tänka på att flytta korten: från draghögen till en hand och vidare till slänghögen.
Ett kort som hamnar i slänghögen finns inte längre i draghögen. I klassdiagrammet lämnar Decks
deal() ifrån sig ett kort, och Players discardHand() tömmer handen och returnerar korten så
att spelbordet kan lägga dem i slänghögen.
Ansvar och objekt
Section titled “Ansvar och objekt”Sist pratar Mats och Johan om vilka objekt som behövs och vad de ansvarar för. Deras resonemang känns igen i klassdiagrammet:
| I inspelningen | I klassdiagrammet |
|---|---|
| Spelbordet samlar given, spelarna, draghögen och slänghögen. | CardTable med fälten dealer, players, deck och discardPile |
app.js skapar ett spelbord med önskat antal spelare. |
new CardTable(numberOfPlayers), sedan playRounds(numberOfRounds) |
| Kortleken kan blanda sig själv. | Deck.shuffle() |
| Kortleken kan tala om hur många kort den innehåller. | Deck.count |
| Man kan be kortleken om ett kort. | Deck.deal() |
| Spelaren, inte kortleken, räknar ut summan av korten. | Player.valueOf() |
Johan ger ett test för om ansvaret hamnat rätt: går kortleken att använda i ett annat kortspel, till
exempel Black Jack eller poker? Då har du antagligen tänkt rätt. Fungerar den bara i Tjugoett har du
troligen lagt för mycket “intelligens” i kortleken eller korten. Deck i klassdiagrammet klarar
testet: inget i den vet något om Tjugoett. Kortleken räknar inte, och korten har ju dessutom redan
lämnat kortleken när summan räknas.
Läs mer om “Examinationsuppgift A3 - Tjugoett” här
Till skillnad från A2 finns det inga givna tester i A3. Artikeln Att testa din kod
visar hur du testar systematiskt för hand. Den ger också en orientering, utanför kursens ram, om hur
enhetstester skrivs med Vitest och hur Vitest står sig mot andra verktyg som Jest och node:test.
Utmaningar
Section titled “Utmaningar”Denna veckans utmaning är lite annorlunda och låter dig skriva en egen algoritm för att lösa ett problem. Därefter kan du jämföra din lösning med andra lösningar som lärare och studenter kommit fram till. Hur bra kommer din lösning att vara och vad kan du lära dig av att studera andra lösningar på samma problem?
Studiefrågor
Section titled “Studiefrågor”Här är ett antal frågor som du kan försöka besvara och diskutera tillsammans med din AI-assistent. Tveka inte att lyfta någon av frågorna till handledningen för att fortsätta diskussionen med lärare och studiekamrater.
- Jag håller på och lär mig JavaScript med objekt och klasser och jag vill bygga upp ett exempelprogram för att studera olika aspekter av objektorientering, jag kommer ge dig instruktioner så kan du föreslå kodlösningar och kommentera dem så kan jag lära mig från dig, ok?
- Jag vill börja med en tärningsklass Dice där jag skall rulla en 6-sidig tärning och skriva ut tärningens värde efter varje slag. Hur hade du gjort?
- Jag vill använda privata medlemmar för att kapsla in min implementation.
- Nu vill jag se hur man kan göra en klass GraphicalDice som ärver från Dice och skriver ut tärningen som utf-8 tecken.
- Nu vill jag skapa en klass för DiceHand och jag vill ha en metod där man kan lägga till tärningar till DiceHand och sedan rulla dem och skriva ut alla samtidigt.
- Lägg till metoder för att beräkna summan av tärningsslaget.
Här är en utskrift från hur det såg ut när jag diskuterade ovan frågor med min AI-assistent Ajj.
Här följer tips till hur du kan prioritera bland kursmaterialet för att komma igång med veckan.
- Försök komma igång med A3 redan denna veckan. Det är mycket att läsa och låt det ta ett par dagar att läsa, bearbeta och fundera på hur uppgiften kan angripas. Det är ingen brådska att komma igång att koda, låt problemlösningen ta sin tid och läsa igenom kravställningen.
- Jobba igenom de blå övningsuppgifterna så att du har koll på grundläggande konstruktioner i en klass och hur man jobbar med ett objekt. Då får du också grundläggande träning i hur man implemementerar klasser utifrån ett klassdiagram.