Programmering

Design af felter og metoder

Denne måneds rate på Designteknikker er den anden i en miniserie af kolonner om design af objekter. I sidste måneds kolonne, der dækkede design af objekter til korrekt initialisering, talte jeg om, hvordan man designer konstruktører og initialiseringer. Denne måned og næste måned vil jeg diskutere designprincipper for klassens faktiske felter og metoder. Derefter skriver jeg om færdiggørere og viser, hvordan jeg designer objekter til korrekt oprydning i slutningen af ​​deres liv.

Materialet til denne artikel (undgåelse af specielle dataværdier, brug af konstanter, minimering af kobling) og den næste artikel (maksimering af samhørighed) kan være kendt for mange læsere, da materialet er baseret på generelle designprincipper, der er helt uafhængige af Java-programmeringssproget. . Ikke desto mindre, fordi jeg har stødt på så meget kode gennem årene, der ikke udnytter disse principper, tror jeg, at de fortjener at blive omformuleret fra tid til anden. Derudover forsøger jeg i denne artikel at vise, hvordan disse generelle principper gælder især Java-sproget.

Design af felter

Ved udformning af felter er hovedreglen at undgå at bruge en variabel til at repræsentere flere attributter i en klasse. Du kan overtræde denne regel ved at angive specielle værdier i en variabel, hver med sin egen specielle betydning.

Som brugt her, an attribut er et kendetegn ved et objekt eller en klasse. To egenskaber ved en Kaffekop objekt kan for eksempel være:

  • Den mængde kaffe, koppen indeholder
  • Uanset om koppen er ren eller snavset

For at se nærmere på denne regel kan du forestille dig, at du designer en Kaffekop klasse til den virtuelle café, der blev beskrevet i sidste måneds Designteknikker kolonne. Antag, at du vil modellere, om en kaffekop i din virtuelle café er blevet vasket og er klar til brug af den næste kunde. Med disse oplysninger ved hånden kan du sikre, at du ikke genbruger en kaffekop, før den er blevet vasket.

Hvis du beslutter, at du kun er ligeglad med, om en kop er blevet vasket, hvis den er tom, kan du bruge en særlig værdi af innerKaffe felt, som normalt bruges til at holde styr på mængden af ​​kaffe i koppen, til at repræsentere en uvasket kop. Hvis 473 milliliter (16 væske ounce) er den maksimale mængde kaffe i din største kop, så er den maksimale værdi af innerKaffe normalt ville være 473. Således kan du bruge en innerKaffe værdi på f.eks. 500 (en særlig værdi) for at angive en tom kop, der ikke er vasket:

// I kildepakke i filfelter / ex1 / CoffeeCup.java klasse CoffeeCup {private int innerCoffee; public boolean isReadyForNextUse () {// Hvis kaffekoppen ikke vaskes, er den // ikke klar til næste brug, hvis (innerCoffee == 500) {returner false; } returner sandt } offentlig ugyldig setCustomerDone () {innerCoffee = 500; // ...} offentlig tomrumsvask () {innerCoffee = 0; // ...} // ...} 

Denne kode vil give Kaffekop modsætter sig den ønskede adfærd. Problemet med denne tilgang er, at specielle værdier ikke let forstås, og de gør koden sværere at ændre. Selvom du beskriver specielle værdier i en kommentar, kan det tage andre programmerere længere tid at forstå, hvad din kode laver. Desuden forstår de måske aldrig din kode. De bruger muligvis din klasse forkert eller ændrer den, så de introducerer en fejl.

For eksempel, hvis nogen senere tilføjer en 20 ounce kop til tilbudene på den virtuelle café, ville det så være muligt at holde op til 592 milliliter (ml) kaffe i en kop. Hvis en programmør tilføjer den nye kopstørrelse uden at indse, at du bruger 500 ml til at indikere, at en kop skal vaskes, er det sandsynligt, at der introduceres en fejl. Hvis en kunde i din virtuelle café købte en 20 ounce kop og derefter tog en stor 92 ml gulp, ville han eller hun så have nøjagtigt 500 ml tilbage i koppen. Kunden ville være chokeret og utilfreds, når koppen efter kun at have drukket 92 ml forsvandt fra hans eller hendes hånd og dukkede op i vasken, klar til vask. Og selvom den programmør, der foretog ændringen, indså, at du brugte en særlig værdi, skulle der vælges en anden særlig værdi for den uvaskede attribut.

En bedre tilgang til denne situation er at have et separat felt til at modellere den separate attribut:

// I kildepakke i filfelter / ex2 / CoffeeCup.java klasse CoffeeCup {private int innerCoffee; private boolske behovVask; public boolean isReadyForNextUse () {// Hvis kaffekoppen ikke vaskes, er den // ikke klar til næste brugsretur! needsWashing; } public void setCustomerDone () {needsWashing = true; // ...} offentlig tomrumsvask () {needsWashing = false; // ...} // ...} 

Her er innerKaffe felt bruges kun til at modellere mængden af ​​kaffe i kopattributten. Attributten kop-behov-vask er modelleret af behovVask Mark. Denne ordning er lettere forstået end den tidligere ordning, der brugte en særlig værdi på innerKaffe og ville ikke forhindre nogen i at udvide den maksimale værdi for innerKaffe.

Brug af konstanter

En anden tommelfingerregel, der skal følges, når der oprettes felter, er at bruge konstanter (statiske endelige variabler) til konstante værdier, der sendes til, returneres fra eller bruges inden for metoder. Hvis en metode forventer et af et begrænset sæt konstante værdier i en af ​​dens parametre, hjælper definition af konstanter med at gøre det mere indlysende for klientprogrammerere, hvad der skal sendes i denne parameter. Ligeledes, hvis en metode returnerer et af et endeligt sæt værdier, gør det at erklære konstanter det mere indlysende for klientprogrammerere, hvad de kan forvente som output. For eksempel er det lettere at forstå dette:

hvis (cup.getSize () == CoffeeCup.TALL) {} 

end det er at forstå dette:

hvis (cup.getSize () == 1) {} 

Du bør også definere konstanter til intern brug ved hjælp af metoderne i en klasse - selvom disse konstanter ikke bruges uden for klassen - så de er lettere at forstå og ændre. Brug af konstanter gør koden mere fleksibel. Hvis du er klar over, at du har beregnet en værdi forkert, og du ikke brugte en konstant, bliver du nødt til at gennemgå din kode og ændre enhver forekomst af den hårdkodede værdi. Hvis du dog brugte en konstant, behøver du kun at ændre den, hvor den er defineret som en konstant.

Konstanter og Java-kompilatoren

En nyttig ting at vide om Java-kompilatoren er, at den behandler statiske endelige felter (konstanter) forskelligt end andre slags felter. Henvisninger til statiske endelige variabler initialiseret til en kompileringstidskonstant løses ved kompileringstid til en lokal kopi af den konstante værdi. Dette gælder for konstanter af alle de primitive typer og af typen java.lang.Streng.

Normalt, når din klasse henviser til en anden klasse - sig, klasse java.lang.Math - Java-kompilatoren placerer symbolske referencer til klasse Matematik ind i klassefilen til din klasse. For eksempel, hvis en metode i din klasse påberåber sig Math.sin (), din klassefil indeholder to symbolske referencer til Matematik:

  • En symbolsk henvisning til klasse Matematik
  • En symbolsk henvisning til Matematik's synd() metode

At udføre koden indeholdt i din klasse, der refererer til Math.sin (), ville JVM skulle indlæse klasse Matematik for at løse de symbolske referencer.

Hvis din kode derimod kun henviste til den statiske endelige klassevariabel PI erklæret i klassen Matematik, ville Java-kompilatoren ikke placere nogen symbolsk henvisning til Matematik i klassefilen til din klasse. I stedet placerer det simpelthen en kopi af den bogstavelige værdi af Math.PI ind i din klasses klassefil. At udføre koden indeholdt i din klasse, der bruger Math.PI konstant, behøver JVM ikke at indlæse klasse Matematik.

Resultatet af denne funktion af Java-kompilatoren er, at JVM ikke behøver at arbejde hårdere for at bruge konstanter, end det gør for at bruge bogstaver. At foretrække konstanter frem for bogstaver er en af ​​de få designretningslinjer, der forbedrer programfleksibilitet uden at risikere nogen forringelse af programydelsen.

Tre slags metoder

Resten af ​​denne artikel vil diskutere teknikker til metodedesign, der vedrører de data, en metode bruger eller ændrer. I denne sammenhæng vil jeg gerne identificere og navngive tre grundlæggende typer metoder i Java-programmer: hjælpemetode det tilstandsvisningsmetode, og tilstandsændringsmetode.

Hjælpemetoden

En hjælpemetode er en klassemetode, der ikke bruger eller ændrer tilstanden (klassevariabler) for sin klasse. Denne form for metode giver simpelthen en nyttig service relateret til dens objektklasse.

Nogle eksempler på hjælpemetoder fra Java API er:

  • (I klassen Heltal) offentlig statisk int toString (int i) - returnerer en ny Snor objekt, der repræsenterer det angivne heltal i radix 10
  • (I klassen Matematik) offentlig statisk indfødt dobbelt cos (dobbelt a) - returnerer den trigonometriske cosinus for en vinkel

Statusvisningsmetoden

En tilstandsvisningsmetode er en klasse eller en instansmetode, der returnerer en visning af den interne tilstand for klassen eller objektet uden at ændre denne tilstand. (Denne form for metode ser bort fra Heisenberg Usikkerhedsprincippet - se Ressourcer, hvis du har brug for en opdatering på dette princip.) En tilstandsvisningsmetode kan simpelthen returnere værdien af ​​en klasse eller en instansvariabel, eller den kan returnere en værdi beregnet ud fra flere klasse- eller forekomstvariabler.

Nogle eksempler på state-view-metoder fra Java API er:

  • (I klassen Objekt) offentlig String toString () - returnerer en strengrepræsentation af objektet
  • (I klassen Heltal) offentlig byte-værdi () - returnerer værdien af Heltal objekt som en byte
  • (I klassen Snor) public int indexOf (int ch) - returnerer indekset inden for strengen i den første forekomst af det angivne tegn

Metoden til tilstandsændring

Tilstandsændringsmetoden er en metode, der kan omdanne tilstanden for den klasse, hvor metoden erklæres, eller, hvis en instansmetode, det objekt, hvorpå den påberåbes. Når en tilstandsændringsmetode påberåbes, repræsenterer den en "begivenhed" for en klasse eller et objekt. Metoden koder "håndterer" begivenheden, hvilket muligvis ændrer klassen eller objektets tilstand.

Nogle eksempler på tilstandsændringsmetoder fra Java API er:

  • (I klassen StringBuffer) offentlig StringBuffer append (int i) - tilføjer strengrepræsentationen af int argument til StringBuffer
  • (I klassen Hashtable) offentlig synkroniseret ugyldig rydde () - rydder Hashtable så den ikke indeholder nøgler
  • (I klassen Vektor) offentlig endelig synkroniseret ugyldig addElement (Objekt obj) - tilføjer den specificerede komponent til slutningen af Vektor, øger størrelsen med en

Minimering af metoden kobling

Bevæbnet med disse definitioner af nytte-, tilstandsvisning og tilstandsændringsmetoder er du klar til diskussion af metodekobling.

Når du designer metoder, skal et af dine mål være at minimere kobling - graden af ​​indbyrdes afhængighed mellem en metode og dens miljø (andre metoder, objekter og klasser). Jo mindre kobling der er mellem en metode og dens omgivelser, jo mere uafhængig er metoden, og jo mere fleksibel er designet.

Metoder som datatransformatorer

For at forstå kobling hjælper det med at tænke på metoder udelukkende som transformatorer af data. Metoder accepterer data som input, udfører operationer på disse data og genererer data som output. En metodes koblingsgrad bestemmes primært af, hvor den får sine inputdata, og hvor den placerer sine outputdata.

Figur 1 viser en grafisk afbildning af metoden som datatransformer: Et dataflowdiagram fra struktureret (ikke objektorienteret) design.

Input og output

En metode i Java kan få inputdata fra mange kilder:

  • Det kan kræve, at den, der ringer op, specificerer sine inputdata som parametre, når de påkaldes
  • Det kan hente data fra alle tilgængelige klassevariabler, såsom klassens egne klassevariabler eller tilgængelige klassevariabler fra en anden klasse
  • Hvis det er en instansmetode, kan den hente instansvariabler fra det objekt, som den blev påberåbt

Ligeledes kan en metode udtrykke sin produktion mange steder:

  • Det kan returnere en værdi, enten en primitiv type eller en objektreference
  • Det kan ændre objekter, der henvises til med referencer, der er sendt som parametre
  • Det kan ændre alle klassevariabler i sin egen klasse eller alle tilgængelige klassevariabler i en anden klasse
  • Hvis det er en instansmetode, kan den ændre alle instansvariabler for det objekt, som den blev påberåbt
  • Det kan kaste en undtagelse

Bemærk, at parametre, returværdier og kastede undtagelser ikke er de eneste slags metodeinput og -output, der er nævnt i ovenstående lister. Instans- og klassevariabler behandles også som input og output. Dette kan synes ikke-intuitivt set fra et objektorienteret perspektiv, fordi adgang til instans- og klassevariabler i Java er "automatisk" (du behøver ikke videregive noget eksplicit til metoden). Når du forsøger at måle en metodes kobling, skal du dog se på typen og mængden af ​​data, der bruges og ændres af koden, uanset om kodens adgang til disse data var "automatisk".

Minimalt koblede hjælpemetoder

Den mindst koblede metode, der er mulig i Java, er en hjælpemetode, der:

  1. Tar kun input fra dets parametre
  2. Udtrykker kun dets output gennem dets parametre eller dens returværdi (eller ved at kaste en undtagelse)
  3. Accepterer som input kun data, der faktisk er nødvendige af metoden
  4. Returnerer som output kun data, der faktisk produceres ved metoden

En god hjælpemetode

For eksempel metoden convertOzToMl () vist nedenfor accepterer en int som eneste input og returnerer en int som sin eneste output: