Hej! I dag vil vi berøre et vigtigt nyt emne: designmønstre . Hvad er disse mønstre? Jeg tror, du skal kende udtrykket " opfind ikke hjulet igen ". Inden for programmering, som på mange andre områder, er der en lang række almindelige situationer. Efterhånden som softwareudviklingen har udviklet sig, er der skabt færdige løsninger, der virker til hver af dem. Disse løsninger kaldes designmønstre. Konventionelt er et mønster en løsning formuleret sådan: "hvis du skal lave X i dit program, så er dette den bedste måde at gøre det på". Der er mange mønstre. Den fremragende bog "Head First Design Patterns", som du bestemt bør stifte bekendtskab med, er dedikeret til dem. Kort fortalt består et mønster af et fælles problem og en tilsvarende løsning, der kan betragtes som en slags standard. I dagens lektion vil vi møde et af disse mønstre: Adapter. Dens navn siger det hele, og du har stødt på adaptere mange gange i det virkelige liv. Nogle af de mest almindelige adaptere er de kortlæsere, som mange computere og bærbare computere har. Antag, at vi har en slags hukommelseskort. Så hvad er problemet? Den ved ikke, hvordan den interagerer med computeren. De deler ikke en fælles grænseflade. Computeren har en USB-port, men vi kan ikke sætte hukommelseskortet i den. Kortet kan ikke sættes i computeren, så vi kan ikke gemme vores billeder, videoer og andre data. En kortlæser er en adapter, der løser dette problem. Den har jo et USB-kabel! I modsætning til selve kortet kan kortlæseren tilsluttes computeren. De deler en fælles grænseflade med computeren: USB. Lad os se, hvordan det ser ud i praksis:
public interface USB {
void connectWithUsbCable();
}
Dette er vores USB-interface med kun én metode til tilslutning via USB.
public class MemoryCard {
public void insert() {
System.out.println("Memory card successfully inserted!");
}
public void copyData() {
System.out.println("The data has been copied to the computer!");
}
}
Dette er vores klasse, der repræsenterer hukommelseskortet. Den har allerede de 2 metoder, vi har brug for, men her er problemet: Den implementerer ikke USB-grænsefladen. Kortet kan ikke indsættes i USB-porten.
public class CardReader implements USB {
private MemoryCard memoryCard;
public CardReader(MemoryCard memoryCard) {
this.memoryCard = memoryCard;
}
@Override
public void connectWithUsbCable() {
this.memoryCard.insert();
this.memoryCard.copyData();
}
}
Og her er vores adapter! Hvad gørCardReader
klasse gør, og hvad gør det præcist til en adapter? Det hele er enkelt. Klassen, der tilpasses (MemoryCard), bliver et af adapterens felter. Dette giver mening. Når vi sætter et hukommelseskort i en kortlæser i det virkelige liv, bliver det også en del af det. I modsætning til hukommelseskortet deler adapteren en grænseflade med computeren. Den har et USB-kabel, dvs den kan tilsluttes andre enheder via USB. Det er derfor, vores CardReader-klasse implementerer USB-grænsefladen. Men hvad sker der præcist i denne metode? Præcis hvad vi skal til at ske! Adapteren delegerer arbejdet til vores hukommelseskort. Faktisk gør adapteren ikke noget selv. En kortlæser har ikke nogen selvstændig funktionalitet. Dens opgave er kun at forbinde computeren og hukommelseskortet for at tillade kortet at gøre sit arbejde - kopiering af filer!connectWithUsbCable()
metode) for at opfylde hukommelseskortets "behov". Lad os lave et klientprogram, der simulerer en person, der ønsker at kopiere data fra et hukommelseskort:
public class Main {
public static void main(String[] args) {
USB cardReader = new CardReader(new MemoryCard());
cardReader.connectWithUsbCable();
}
}
Så hvad fik vi? Konsoludgang:
Memory card successfully inserted!
The data has been copied to the computer!
Fremragende. Vi nåede vores mål! Her er et link til en video med information om adaptermønsteret:
Læser og forfatter abstrakt klasser
Nu vender vi tilbage til vores yndlingsaktivitet: at lære om et par nye klasser til at arbejde med input og output :) Jeg spekulerer på, hvor mange vi allerede har lært om. I dag vil vi tale omReader
og Writer
klasser. Hvorfor netop de klasser? Fordi de er relateret til vores tidligere afsnit om adaptere. Lad os undersøge dem mere detaljeret. Vi starter med Reader
. Reader
er en abstrakt klasse, så vi vil ikke være i stand til at oprette objekter eksplicit. Men du er faktisk allerede bekendt med det! Du er jo godt bekendt med klasserne BufferedReader
og InputStreamReader
, som er dens efterkommere :)
public class BufferedReader extends Reader {
…
}
public class InputStreamReader extends Reader {
…
}
Klassen InputStreamReader
er en klassisk adapter. Som du sikkert husker, kan vi videregive et InputStream
objekt til dets konstruktør. For at gøre dette bruger vi normalt System.in
variablen:
public static void main(String[] args) {
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
Men hvad InputStreamReader
gør? Som enhver adapter konverterer den en grænseflade til en anden. I dette tilfælde grænsefladen InputStream
til Reader
grænsefladen. I første omgang har vi InputStream
klassen. Det fungerer godt, men du kan kun bruge det til at læse individuelle bytes. Derudover har vi en Reader
abstrakt klasse. Det har nogle meget nyttige funktioner - det ved, hvordan man læser tegn! Vi har bestemt brug for denne evne. Men her står vi over for det klassiske problem, der normalt løses af adaptere - inkompatible grænseflader. Hvad betyder det? Lad os tage et kig på Oracle-dokumentationen. Her er klassens metoder InputStream
. Et sæt metoder er præcis, hvad en grænseflade er. Som du kan se, har denne klasse enread()
metode (faktisk nogle få varianter), men den kan kun læse bytes: enten individuelle bytes eller flere bytes ved hjælp af en buffer. Men denne mulighed passer ikke os - vi vil gerne læse karakterer. Vi har brug for den funktionalitet, der allerede er implementeret i den Reader
abstrakte klasse . Det kan vi også se i dokumentationen. Men grænsefladerne InputStream
og Reader
er inkompatible! Som du kan se, har hver implementering af read()
metoden forskellige parametre og returværdier. Og det er her, vi har brug for InputStreamReader
! Det vil fungere som en adapter mellem vores klasser. Som i eksemplet med kortlæseren, som vi overvejede ovenfor, sætter vi en instans af klassen, der er tilpasset "inde i" adapterklassen, dvs. vi sender en til dens konstruktør. I det foregående eksempel satte vi et MemoryCard
objekt ind i CardReader
. Nu sender vi et InputStream
objekt til InputStreamReader
konstruktøren! Vi bruger vores velkendte System.in
variabel som InputStream
:
public static void main(String[] args) {
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
Og faktisk, ser vi på dokumentationen for InputStreamReader
, kan vi se, at tilpasningen lykkedes :) Nu har vi metoder til at læse karakterer til vores rådighed. Og selvom vores System.in
objekt (strømmen bundet til tastaturet) oprindeligt ikke tillod dette, løste sprogets skabere dette problem ved at implementere adaptermønsteret. Den Reader
abstrakte klasse har ligesom de fleste I/O-klasser en tvillingebror — Writer
. Det har den samme store fordel som Reader
— det giver en praktisk grænseflade til at arbejde med karakterer. Med outputstrømme ser problemet og dets løsning ud på samme måde som med inputstrømme. Der er en OutputStream
klasse, der kun kan skrive bytes, der er enWriter
abstrakt klasse, der ved, hvordan man arbejder med tegn, og der er to inkompatible grænseflader. Dette problem er igen løst af adaptermønsteret. Vi bruger OutputStreamWriter
klassen til nemt at tilpasse de to grænseflader af Writer
og OutputStream
klasserne til hinanden. Efter at have sendt en OutputStream
bytestrøm til konstruktøren, kan vi bruge en OutputStreamWriter
til at skrive tegn i stedet for bytes!
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
OutputStreamWriter streamWriter = new OutputStreamWriter(new FileOutputStream("C:\\Users\\Username\\Desktop\\test.txt"));
streamWriter.write(32144);
streamWriter.close();
}
}
Vi skrev tegnet med kode 32144 (綐) til vores fil, hvilket eliminerede behovet for at arbejde med bytes :) Det var det for i dag. Vi ses i de næste lektioner! :)
GO TO FULL VERSION