Mörkt tema
Klasser (objektorienterad programmering) đ€ż â
OBS!
Detta kan du betrakta som fördjupning. Kolla igenom om du Àr intresserad, men det Àr inget som krÀvs för utbildningen nu. Bra att kÀnna till nÄgonstans i bakhuvudet.
đĄ Introduktion till Ă€mnet (flipped classroom) â
ⰠTidsÄtgÄng: c.a. 34 min
Klasser (27 min) đ€ż â
Interface (7 min) đ€ż â
đ LĂ€sanvisningar â
- JavaScript - The Definitive Guide: Kapitel 9
- JavaScript - The Definitive Guide: Kapitel 10
- đ Learning TypeScript av Josh Goldberg: Classes
đïž Ăvningsuppgift â
Nedan följer en övning i sÄvÀl att skapa klasser som att "lÀsa terminologi".
- Skapa en generisk klass "Animal" och lÀgg till tvÄ egenskaper pÄ klassen (t.ex. namn och Älder).
- LÀgg till en konstruktor som sparar de tvÄ egenskaperna i lokala variabler.
- Skapa en klass "Cat" som Àrver frÄn "Animal". Skapa en instans av klassen, t.ex.
nyan. - Utöka nu "Animal" med en metod som heter
makeSound(). Som default ska djuret inte göra nÄgot ljud. (AnvÀndconsole.logför att göra ljud.) - Skapa en klass "Fox" som Àrver frÄn "Animal".
- "Override:a" metoden i klassen "Cat", sÄ att den lÄter som en katt.
- Gör samma sak för "Fox".
Exempellösning
ts
class Animal {
name: string;
age: number;
constructor(_name: string, _age: number) {
this.name = _name;
this.age = _age;
}
makeSound() {
console.log('');
}
}
class Cat extends Animal {
makeSound(): void {
console.log('đ± Meow!');
}
}
class Fox extends Animal {
makeSound(): void {
console.log('đŠ What does the fox say?');
}
}
const nyan = new Cat('Nyan', 10);
nyan.makeSound();
const ylvis = new Fox('Ylvis', 5);
ylvis.makeSound();1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Generera en JavaScript-fil av din TypeScript-fil med hjÀlp av npx tsc mapp/filnamnet.ts.
Kika lite pÄ vad resultatet i filen blir. Du behöver inte sÀtta dig in i koden, men notera bara vad TypeScript ser ut som i vanlig JavaScript.
Om du vill lÀra dig mer om koddokumentation sÄ föreslÄr jag att du kikar pÄ JSDoc, och hur det Àr brukligt att dokumentera sin kod.
â AvstĂ€mning â
AnvÀnd denna lista för att "checka av" att du förstÄr modulens koncept.
Terminologi
- Objekt-orienterad programmering (OOP)
- Inheritance (arv)
- Interface
Du Ă€r klar nĂ€râŠ
- Du förstÄr (ungefÀr) vad en klass Àr
â VĂ„ga FrĂ„ga â
đ Resurser â
đ Introduktion till klasser
Med interface i TypeScript berörde vi redan en del av nÀsta koncept: klasser. Inom programmering finns det flera olika sÀtt att programmera pÄ, och programmeringssprÄk delas in i olika "kategorier". Man pratar ofta om t.ex. funktionella och objekt-orienterade programmeringssprÄk. Vissa sprÄk kan bara programmeras objekt-orienterat (som t.ex. Java och C#), medan andra sprÄk gÄr att programmera med bÄda sÀtten (t.ex. PHP och JavaScript) och andra sprÄk Àr enbart funktionella.
Ofta i programmering har vi en sak (ett objekt, ej att förvÀxla med {}) som har gemensamma egenskaper, som vi behöver duplicera/skapa kopior av. Dessa kopior ska följa en viss mall.
TĂ€nk dig exempelvis en (fransk-engelsk) spelkortlek som bestĂ„r av ett antal kort. đ Varje kort har en fĂ€rg/svit och en valör (frĂ„n 1 till 13). För detta skulle vi kunna deklarera en klass:
ts
// Obegriplig copy-paste TypeScript-kod som lÄter oss definiera att siffror kan endast vara mellan 1 och 13
// https://stackoverflow.com/a/39495173/9306514
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
? Acc[number]
: Enumerate<N, [...Acc, Acc['length']]>;
type IntRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;
// INTRESSANT HĂRIFRĂ
N đ
class SingleCard {
suit: 'clubs' | 'diamonds' | 'hearts' | 'spades';
rank: IntRange<1, 13>;
}
const aceOfSpades = new SingleCard();
const ace2 = new SingleCard();1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Men, nu berÀttar vi inte vad kortet ska vara nÀr kortet skapas. För det behöver vi anvÀnda en konstruktor-metod (constructor). Det Àr en "magisk" funktion som körs automatiskt nÀr vi skapar en ny instans av en klass, dvs. nÀr vi skriver new.
ts
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
? Acc[number]
: Enumerate<N, [...Acc, Acc['length']]>;
type IntRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;
// INTRESSANT HĂRIFRĂ
N đ
class SingleCard {
suit: 'clubs' | 'diamonds' | 'hearts' | 'spades';
rank: IntRange<1, 13>;
constructor(suit: 'clubs' | 'diamonds' | 'hearts' | 'spades', rank: IntRange<1, 13>) {
this.suit = suit;
this.rank = rank;
}
}
const aceOfSpades = new SingleCard('spades', 1);
const ace2 = new SingleCard('spades', 2);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
this Àr ett nyckelord som Äsyftar till sjÀlva "instansen av klassen". This Àr lite som att "peka pÄ sig sjÀlv".
Nu har vi dock en del kodupprepning i koden (t.ex. sviten och rankingen). LÄt oss generalisera koden genom att refaktorera den:
ts
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
? Acc[number]
: Enumerate<N, [...Acc, Acc['length']]>;
type IntRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;
type Suits = 'clubs' | 'diamonds' | 'hearts' | 'spades';
type CardRange = IntRange<1, 13>;
class SingleCard {
suit: Suits;
rank: CardRange;
constructor(suit: Suits, rank: CardRange) {
this.suit = suit;
this.rank = rank;
}
}
const aceOfSpades = new SingleCard('spades', 1);
const ace2 = new SingleCard('spades', 2);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Nu kan vi skapa hela sviten automatiserat:
ts
const spades: SingleCard[] = [];
for (let i = 0; i < 13; i++) {
const card = new SingleCard('spades', (i + 1) as CardRange);
spades.push(card);
}1
2
3
4
5
6
2
3
4
5
6
I objekt-orienterad programmering (OOP) finns det ocksÄ ett koncept som heter "inheritance", eller "arv" pÄ svenska. Det innebÀr att vi kan Àrva egenskaper frÄn en klass, och pÄ sÄ vis utöka den. Vi börjar med att förenkla vÄr kortlek, sÄ att vi skapar en till klass som har exakt samma egenskaper som "SingleCard", men vi behöver inte skicka med vilken typ av kort det Àr, för det tar vÄr nya klass SpadesCard hand om automatiskt:
ts
class SpadesCard extends SingleCard {
constructor(rank: CardRange) {
super('spades', rank);
}
}
const spades: SingleCard[] = [];
for (let i = 0; i < 13; i++) {
const card = new SpadesCard((i + 1) as CardRange);
spades.push(card);
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
HÀr gör vi ett anrop till super() inne i konstruktorn, vilket alltsÄ kallar pÄ "förÀldra-klassens" konstruktor, och utför det som ska göras dÀr.
Om vi nu skulle lÀgga till en metod i vÄr huvudklass SingleCard, sÄ kan vi anropa den Àven i SpadesCard utan att ha deklarerat den:
ts
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
? Acc[number]
: Enumerate<N, [...Acc, Acc['length']]>;
type IntRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;
type Suits = 'clubs' | 'diamonds' | 'hearts' | 'spades';
type CardRange = IntRange<1, 13>;
class SingleCard {
suit: Suits;
rank: CardRange;
used: boolean = false;
constructor(suit: Suits, rank: CardRange) {
this.suit = suit;
this.rank = rank;
}
discard() {
console.log(`${this.suit} ${this.rank} has been discarded`);
this.used = true;
}
}
class SpadesCard extends SingleCard {
constructor(rank: CardRange) {
super('spades', rank);
}
}
const spades: SingleCard[] = [];
for (let i = 0; i < 13; i++) {
const card = new SpadesCard((i + 1) as CardRange);
spades.push(card);
}
spades[0].discard(); // spades 1 has been discarded1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Vi har alltsÄ Àven Àrvt metoden.
đ Klasser och interface
Ibland behöver vi generalisera nÄgonting och anvÀnda det pÄ flera klasser, men vi vill inte Àrva alla egenskaperna utan ha olika egenskaper pÄ klasserna - men dela vissa gemensamma egenskaper.
DÄ kan vi anvÀnda interface och "implementera" detta pÄ olika klasser. Vi kikar pÄ det genom kod (kopierat frÄn boken):
ts
interface Learner {
name: string;
study(hours: number): void;
}
class Student implements Learner {
name: string;
constructor(name: string) {
this.name = name;
}
study(hours: number) {
for (let i = 0; i < hours; i+= 1) {
console.log("...studying...");
}
}
}
class Slacker implements Learner {
// ~~~~~~~
// Error: Class 'Slacker' incorrectly implements interface 'Learner'.
// Property 'study' is missing in type 'Slacker'
// but required in type 'Learner'.
name = "Rocky";
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26