Abstrakte klasser i Java

1. Oversigt

Der er mange tilfælde ved implementering af en kontrakt, hvor vi ønsker at udsætte nogle dele af implementeringen for at blive afsluttet senere. Vi kan let opnå dette i Java gennem abstrakte klasser.

I denne vejledning lærer vi det grundlæggende i abstrakte klasser i Java, og i hvilke tilfælde de kan være nyttige.

2. Nøglebegreber til abstrakte klasser

Før du dykker ned i hvornår du skal bruge en abstrakt klasse, lad os se på deres mest relevante egenskaber:

  • Vi definerer en abstrakt klasse med abstrakt forud for klasse nøgleord
  • En abstrakt klasse kan underklasseres, men den kan ikke instantieres
  • Hvis en klasse definerer en eller flere abstrakt metoder, så skal klassen selv erklæres abstrakt
  • En abstrakt klasse kan erklære både abstrakte og konkrete metoder
  • En underklasse afledt af en abstrakt klasse skal enten implementere alle basisklassens abstrakte metoder eller være abstrakt i sig selv

For bedre at forstå disse begreber opretter vi et simpelt eksempel.

Lad os få vores base abstrakte klasse til at definere det abstrakte API for et brætspil:

offentlig abstrakt klasse BoardGame {// ... felterklæringer, konstruktører offentlig abstrakt ugyldigt spil (); // ... konkrete metoder}

Derefter kan vi oprette en underklasse, der implementerer Spil metode:

offentlig klasse Checkers udvider BoardGame {public void play () {// ... implementering}}

3. Hvornår skal man bruge abstrakte klasser?

Nu, lad os analysere et par typiske scenarier, hvor vi foretrækker abstrakte klasser frem for grænseflader og konkrete klasser:

  • Vi ønsker at indkapsle nogle almindelige funktioner på ét sted (genbrug af kode), som flere relaterede underklasser deler
  • Vi skal delvist definere en API, som vores underklasser let kan udvide og forfine
  • Underklasserne skal arve en eller flere almindelige metoder eller felter med beskyttede adgangsmodifikatorer

Lad os huske på, at alle disse scenarier er gode eksempler på fuld, arvbaseret overholdelse af Open / Closed-princippet.

Da brugen af ​​abstrakte klasser implicit beskæftiger sig med basetyper og undertyper, udnytter vi desuden polymorfisme.

Bemærk, at genbrug af kode er en meget overbevisende grund til at bruge abstrakte klasser, så længe “is-a” -forholdet inden for klassehierarkiet bevares.

Og Java 8 tilføjer endnu en rynke med standardmetoder, som nogle gange kan træde i stedet for at skulle oprette en abstrakt klasse helt.

4. Et eksempel på hierarki af fillæsere

For at forstå klarere den funktionalitet, som abstrakte klasser bringer til bordet, lad os se på et andet eksempel.

4.1. Definition af en base abstrakt klasse

Så hvis vi ønskede at have flere typer fillæsere, kunne vi muligvis oprette en abstrakt klasse, der indkapsler det, der er fælles for fillæsning:

offentlig abstrakt klasse BaseFileReader {beskyttet sti filePath; beskyttet BaseFileReader (sti filePath) {this.filePath = filePath; } offentlig sti getFilePath () {return filePath; } offentlig liste readFile () kaster IOException {return Files.lines (filePath) .map (dette :: mapFileLine) .collect (Collectors.toList ()); } beskyttet abstrakt String mapFileLine (strengelinje); }

Bemærk, at vi har lavet filePath beskyttet, så underklasserne kan få adgang til det, hvis det er nødvendigt. Vigtigere, vi har efterladt noget ugjort: hvordan man faktisk analyserer en tekstlinje fra filens indhold.

Vores plan er enkel: Selvom vores konkrete klasser ikke hver især har en særlig måde at gemme filstien på eller gå gennem filen, vil de hver især have en speciel måde at transformere hver linje på.

Ved første øjekast, BaseFileReader kan virke unødvendige. Det er dog grundlaget for et rent, let udvideligt design. Fra det, Vi kan nemt implementere forskellige versioner af en fillæser, der kan fokusere på deres unikke forretningslogik.

4.2. Definition af underklasser

En naturlig implementering er sandsynligvis en, der konverterer en fils indhold til små bogstaver:

offentlig klasse LowercaseFileReader udvider BaseFileReader {public LowercaseFileReader (Path filePath) {super (filePath); } @ Override public String mapFileLine (String line) {return line.toLowerCase (); }}

Eller en anden kan være en, der konverterer en fils indhold til store bogstaver:

public class UppercaseFileReader udvider BaseFileReader {public UppercaseFileReader (Path filePath) {super (filePath); } @ Override public String mapFileLine (String line) {return line.toUpperCase (); }}

Som vi kan se fra dette enkle eksempel, hver underklasse kan fokusere på sin unikke adfærd uden at skulle specificere andre aspekter af fillæsning.

4.3. Brug af en underklasse

Endelig er brugen af ​​en klasse, der arver fra en abstrakt, ikke anderledes end nogen anden konkret klasse:

@Test offentlig ugyldighed givenLowercaseFileReaderInstance_whenCalledreadFile_thenCorrect () kaster undtagelse {URL placering = getClass (). GetClassLoader (). GetResource ("filer / test.txt") Sti = Paths.get (location.toURI ()); BaseFileReader små bogstaverFileReader = nye små bogstaverFileReader (sti); assertThat (smallcaseFileReader.readFile ()). erInstanceOf (List.class); }

For enkelheds skyld er målfilen placeret under src / main / ressourcer / filer folder. Derfor brugte vi en applikationsklasser til at få stien til eksempelfilen. Du er velkommen til at tjekke vores tutorial om klasselæssere i Java.

5. Konklusion

I denne hurtige artikel vi lærte det grundlæggende i abstrakte klasser i Java, og hvornår vi skulle bruge dem til at opnå abstraktion og indkapsle fælles implementering på et enkelt sted.

Som normalt er alle kodeeksempler vist i denne vejledning tilgængelige på GitHub.