Programmering

Designe felt og metoder

Denne månedens avdrag på Designteknikker er den andre i en miniserie av kolonner om å designe objekter. I forrige måneds kolonne, som dekket design av objekter for riktig initialisering, snakket jeg om hvordan man designer konstruktører og initialiserere. Denne måneden og neste måned vil jeg diskutere designprinsipper for de faktiske feltene og metodene i klassen. Etter det vil jeg skrive om sluttbehandlere og vise hvordan du designer objekter for riktig opprydding på slutten av livet.

Materialet for denne artikkelen (unngå spesielle dataverdier, bruk av konstanter, minimering av kobling) og neste artikkel (maksimering av kohesjon) kan være kjent for mange lesere, siden materialet er basert på generelle designprinsipper som er ganske uavhengige av Java-programmeringsspråket. . Likevel, fordi jeg har opplevd så mye kode gjennom årene som ikke utnytter disse prinsippene, tror jeg de fortjener å bli omformulert fra tid til annen. I tillegg prøver jeg i denne artikkelen å vise hvordan disse generelle prinsippene gjelder spesielt Java-språket.

Designe felt

Ved utforming av felt er den viktigste tommelfingerregelen å unngå å bruke en variabel til å representere flere attributter til en klasse. Du kan bryte denne regelen ved å angi spesielle verdier i en variabel, hver med sin egen spesielle betydning.

Som brukt her, an Egenskap er et kjennetegn ved et objekt eller klasse. To attributter av en Kaffe kopp objekt, for eksempel, kan være:

  • Mengden kaffe koppen inneholder
  • Enten koppen er ren eller skitten

For å se nærmere på denne regelen, forestill deg at du designer en Kaffe kopp klasse for den virtuelle kafeen beskrevet i forrige måneds Designteknikker kolonne. Anta at du vil modellere om en kaffekopp i den virtuelle kafeen din er vasket og er klar til bruk av neste kunde. Med denne informasjonen tilgjengelig kan du sikre at du ikke bruker en kaffekopp på nytt før den er vasket.

Hvis du bestemmer deg for at du bare bryr deg om en kopp er vasket eller ikke hvis den er tom, kan du bruke en spesiell verdi av innerCoffee felt, som vanligvis brukes til å holde rede på mengden kaffe i koppen, for å representere en uvasket kopp. Hvis 473 milliliter (16 væske ounces) er den maksimale mengden kaffe i den største koppen din, er maksimumsverdien på innerKaffe ville normalt være 473. Dermed kan du bruke en innerKaffe verdi av for eksempel 500 (en spesiell verdi) for å indikere en tom kopp som ikke er vasket:

// I kildepakke i filfelt / ex1 / CoffeeCup.java klasse CoffeeCup {private int innerCoffee; public boolean isReadyForNextUse () {// Hvis kaffekoppen ikke vaskes, så er den // ikke klar til neste bruk hvis (innerCoffee == 500) {return false; } returner sant; } offentlig ugyldig setCustomerDone () {innerCoffee = 500; // ...} public void wash () {innerCoffee = 0; // ...} // ...} 

Denne koden vil gi Kaffe kopp motsetter seg ønsket oppførsel. Problemet med denne tilnærmingen er at spesielle verdier ikke lett forstås, og de gjør koden vanskeligere å endre. Selv om du beskriver spesielle verdier i en kommentar, kan det ta andre programmerere lenger tid å forstå hva koden din gjør. Videre kan de aldri forstå koden din. De kan bruke klassen din feil eller endre den slik at de introduserer en feil.

For eksempel, hvis noen senere legger til en 20 unse kopp til tilbudene til den virtuelle kaféen, ville det være mulig å holde opptil 592 ml kaffe i en kopp. Hvis en programmerer legger til den nye koppstørrelsen uten å innse at du bruker 500 ml for å indikere at en kopp trenger vask, er det sannsynlig at en feil vil bli introdusert. Hvis en kunde i din virtuelle kafé kjøpte en 20 unse kopp, og deretter tok en stor 92 ml slurk, ville han eller hun da ha nøyaktig 500 ml igjen i koppen. Kunden ville bli sjokkert og misfornøyd når koppen etter å ha drukket bare 92 ml forsvant fra hans eller hennes hånd og dukket opp i vasken, klar til å bli vasket. Og selv om programmereren som gjorde endringen skjønte at du brukte en spesiell verdi, måtte det velges en annen spesiell verdi for det uvaskede attributtet.

En bedre tilnærming til denne situasjonen er å ha et eget felt for å modellere det separate attributtet:

// I kildepakke i filfelt / ex2 / CoffeeCup.java klasse CoffeeCup {private int innerCoffee; private boolske behovVasking; public boolean isReadyForNextUse () {// Hvis kaffekoppen ikke vaskes, så er den // ikke klar for neste gangs retur! needsWashing; } public void setCustomerDone () {needsWashing = true; // ...} public void wash () {needsWashing = false; // ...} // ...} 

Her er innerKaffe feltet brukes bare for å modellere mengden kaffe i koppattributtet. Attributtet kopp-behov-vask er modellert av needsWashing felt. Denne ordningen er lettere forstått enn den forrige ordningen, som brukte en spesiell verdi av innerCoffee og ville ikke hindre noen i å utvide maksimumsverdien for innerKaffe.

Bruke konstanter

En annen tommelfingerregel som skal følges når du oppretter felt, er å bruke konstanter (statiske endelige variabler) for konstante verdier som sendes til, returneres fra eller brukes i metoder. Hvis en metode forventer en av et endelig sett med konstante verdier i en av parametrene, vil definering av konstanter bidra til å gjøre det mer åpenbart for klientprogrammerere hva som må sendes i den parameteren. På samme måte, hvis en metode returnerer en av et endelig sett med verdier, vil det å erklære konstanter gjøre det mer åpenbart for klientprogrammerere hva de kan forvente som utdata. For eksempel er det lettere å forstå dette:

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

enn det er å forstå dette:

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

Du bør også definere konstanter for intern bruk ved hjelp av metodene i en klasse - selv om disse konstantene ikke brukes utenfor klassen - slik at de er lettere å forstå og endre. Bruk av konstanter gjør koden mer fleksibel. Hvis du skjønner at du feilberegnet en verdi og ikke brukte en konstant, må du gå gjennom koden din og endre hver forekomst av den hardkodede verdien. Hvis du brukte en konstant, trenger du bare å endre den der den er definert som en konstant.

Konstanter og Java-kompilatoren

En nyttig ting å vite om Java-kompilatoren er at den behandler statiske sluttfelt (konstanter) annerledes enn andre typer felt. Referanser til statiske endelige variabler initialisert til en kompileringstidskonstant løses ved kompileringstid til en lokal kopi av konstantverdien. Dette gjelder for konstanter av alle primitive typer og av typen java.lang.Streng.

Normalt når klassen din refererer til en annen klasse - si klasse java.lang.Math - Java-kompilatoren plasserer symbolske referanser til klassen Matte inn i klassefilen for klassen din. For eksempel hvis en metode i klassen din påberoper deg Math.sin (), vil klassefilen din inneholde to symbolske referanser til Matte:

  • En symbolsk referanse til klasse Matte
  • En symbolsk referanse til Mattes synd() metode

Å utføre koden i klassen din som refererer til Math.sin (), ville JVM trenge å laste klasse Matte for å løse de symbolske referansene.

Hvis koden derimot bare refererte til den statiske endelige klassevariabelen PI erklært i klassen Matte, ville Java-kompilatoren ikke plassere noen symbolsk referanse til Matte i klassefilen for klassen din. I stedet ville det ganske enkelt plassere en kopi av den bokstavelige verdien av Math.PI inn i klassens klassefil. Å utføre koden i klassen din som bruker Math.PI konstant, trenger ikke JVM å laste klasse Matte.

Resultatet av denne funksjonen til Java-kompilatoren er at JVM ikke trenger å jobbe noe vanskeligere for å bruke konstanter enn for bokstaver. Å foretrekke konstanter fremfor bokstaver er en av få designretningslinjer som forbedrer programfleksibiliteten uten å risikere forringelse av programytelsen.

Tre typer metoder

Resten av denne artikkelen vil diskutere teknikker for metodedesign som er opptatt av dataene en metode bruker eller endrer. I denne sammenhengen vil jeg identifisere og nevne tre grunnleggende typer metoder i Java-programmer: verktøymetode de state-view-metoden, og tilstandsendringsmetode.

Verktøymetoden

En verktøymetode er en klassemetode som ikke bruker eller endrer tilstanden (klassevariabler) til klassen. Denne typen metode gir rett og slett en nyttig tjeneste relatert til objektklassen.

Noen eksempler på verktøymetoder fra Java API er:

  • (I klassen Heltall) offentlig statisk int toString (int i) - returnerer en ny String objekt som representerer det angitte heltallet i radix 10
  • (I klassen Matte) offentlig statisk innfødt dobbelt cos (dobbel a) - returnerer den trigonometriske cosinusen til en vinkel

State-view-metoden

En tilstandsvisningsmetode er en klasse- eller forekomstmetode som returnerer en visning av den interne tilstanden til klassen eller objektet, uten å endre denne tilstanden. (Denne typen metode ser bort fra Heisenberg Usikkerhetsprinsippet - se Ressurser hvis du trenger en oppfriskning på dette prinsippet.) En tilstandssynsmetode kan ganske enkelt returnere verdien til en klasse eller forekomstvariabel, eller den kan returnere en verdi beregnet fra flere klasse- eller forekomstvariabler.

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

  • (I klassen Gjenstand) offentlig streng til streng () - returnerer en strengrepresentasjon av objektet
  • (I klassen Heltall) offentlig byte-verdi () - returnerer verdien av Heltall objekt som en byte
  • (I klassen String) public int indexOf (int ch) - returnerer indeksen i strengen til den første forekomsten av det angitte tegnet

Metoden for statsendring

Metoden for tilstandsendring er en metode som kan transformere tilstanden til klassen som metoden blir deklarert i, eller, hvis en forekomstmetode, objektet som den blir påkalt på. Når en tilstandsendringsmetode påkalles, representerer den en "hendelse" til en klasse eller et objekt. Koden for metoden "håndterer" hendelsen, og kan potensielt endre tilstanden til klassen eller objektet.

Noen eksempler på tilstandsendringsmetoder fra Java API er:

  • (I klassen StringBuffer) offentlig StringBuffer vedlegg (int i) - legger til strengrepresentasjonen av int argument til StringBuffer
  • (I klassen Hashtable) offentlig synkronisert ugyldig slett () - rydder Hashtable slik at den ikke inneholder noen nøkler
  • (I klassen Vector) offentlig endelig synkronisert ugyldig addElement (Objekt obj) - legger til den spesifiserte komponenten til slutten av Vector, øker størrelsen med en

Minimering av metoden kobling

Bevæpnet med disse definisjonene av verktøy, tilstandssyn og tilstandsendringsmetoder, er du klar for diskusjon om metodekobling.

Når du designer metoder, bør et av målene dine være å minimere kobling - graden av gjensidig avhengighet mellom en metode og dens omgivelser (andre metoder, objekter og klasser). Jo mindre kobling det er mellom en metode og dens omgivelser, jo mer uavhengig er metoden, og jo mer fleksibel er designen.

Metoder som datatransformatorer

For å forstå kobling hjelper det å tenke på metoder som transformatorer av data. Metoder aksepterer data som inndata, utfører operasjoner på disse dataene og genererer data som utdata. Koblingsgraden til en metode bestemmes først og fremst av hvor den får inndataene sine og hvor den legger utdataene.

Figur 1 viser en grafisk skildring av metoden som datatransformator: Et dataflytdiagram fra strukturert (ikke objektorientert) design.

Inngang og utgang

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

  • Det kan kreve at den som ringer spesifiserer inngangsdataene sine som parametere når den blir påkalt
  • Det kan hente data fra alle tilgjengelige klassevariabler, for eksempel klassens egne klassevariabler eller tilgjengelige klassevariabler i en annen klasse
  • Hvis det er en forekomstmetode, kan den hente forekomstvariabler fra objektet som den ble påkalt på

På samme måte kan en metode uttrykke sin produksjon mange steder:

  • Den kan returnere en verdi, enten en primitiv type eller en objektreferanse
  • Det kan endre objekter referert til av referanser sendt inn som parametere
  • Det kan endre alle klassevariabler i sin egen klasse eller tilgjengelige klassevariabler i en annen klasse
  • Hvis det er en forekomstmetode, kan den endre alle forekomstvariabler av objektet som den ble påkalt på
  • Det kan kaste et unntak

Vær oppmerksom på at parametere, returverdier og unntak som er kastet, ikke er de eneste metodene som er nevnt i listene ovenfor. Forekomst- og klassevariabler blir også behandlet som input og output. Dette kan virke ikke-intuitivt fra et objektorientert perspektiv, fordi tilgang til forekomst- og klassevariabler i Java er "automatisk" (du trenger ikke å gi noe eksplisitt til metoden). Når du prøver å måle koblingen til en metode, må du imidlertid se på typen og mengden data som brukes og endres av koden, uavhengig av om kodens tilgang til disse dataene var "automatisk".

Minimalt koblede bruksmetoder

Den minst koblede metoden som er mulig i Java er en verktøymetode som:

  1. Tar kun inndata fra parametrene
  2. Uttrykker bare utdataene gjennom parametrene eller returverdien (eller ved å kaste et unntak)
  3. Aksepterer kun inndata som faktisk er nødvendig av metoden
  4. Returnerer som utdata bare data som faktisk er produsert av metoden

En god verktøymetode

For eksempel metoden convertOzToMl () vist nedenfor godtar en int som eneste inngang og returnerer en int som eneste utgang:

$config[zx-auto] not found$config[zx-overlay] not found