Programmering

Innkapsling skjuler ikke informasjon

Ord er glatte. Som Humpty Dumpty proklamerte i Lewis Carrolls Gjennom glasset, "Når jeg bruker et ord, betyr det akkurat hva jeg velger det å bety - verken mer eller mindre." Gjerne den vanlige bruken av ordene innkapsling og informasjon gjemmer seg ser ut til å følge den logikken. Forfattere skiller sjelden mellom de to og hevder ofte direkte at de er de samme.

Gjør det det slik? Ikke for meg. Var det bare et spørsmål om ord, ville jeg ikke skrive et nytt ord om saken. Men det er to forskjellige begreper bak disse begrepene, begreper frembragt hver for seg og best forstått separat.

Innkapsling refererer til samling av data med metodene som fungerer på disse dataene. Ofte tolkes den definisjonen slik at dataene på en eller annen måte er skjult. I Java kan du ha innkapslede data som ikke er skjult i det hele tatt.

Skjuling av data er imidlertid ikke det fulle omfanget av skjult informasjon. David Parnas introduserte først begrepet informasjon som skjulte seg rundt 1972. Han argumenterte for at de primære kriteriene for systemmodularisering burde gjelde skjul av kritiske designbeslutninger. Han understreket å skjule "vanskelige designbeslutninger eller designbeslutninger som sannsynligvis vil endre seg." Å skjule informasjon på den måten isolerer klienter fra å kreve intim kunnskap om designet for å bruke en modul, og fra effekten av å endre disse beslutningene.

I denne artikkelen undersøker jeg skillet mellom innkapsling og informasjon som skjuler seg gjennom utvikling av eksempelkode. Diskusjonen viser hvordan Java legger til rette for innkapsling og undersøker de negative konsekvensene av innkapsling uten at data skjuler seg. Eksemplene viser også hvordan du kan forbedre klassedesign gjennom prinsippet om informasjonsskjuling.

Posisjonsklasse

Med en økende bevissthet om det trådløse internettets enorme potensial, forventer mange eksperter at stedsbaserte tjenester vil gi mulighet for den første appen for trådløs killer. Som eksempler på denne artikkelen har jeg valgt en klasse som representerer den geografiske plasseringen av et punkt på jordoverflaten. Som en domeneenhet heter klassen Posisjon, representerer GPS-informasjon (Global Position System). Et første kutt i klassen ser så enkelt ut som:

offentlig klasse Posisjon {offentlig dobbelt bredde; offentlig dobbel lengdegrad; } 

Klassen inneholder to dataelementer: GPS breddegrad og lengdegrad. Akkurat nå, Posisjon er ikke noe mer enn en liten pose med data. Ikke desto mindre, Posisjon er en klasse, og Posisjon objekter kan instantiseres ved hjelp av klassen. For å bruke disse gjenstandene, klasse PositionUtility inneholder metoder for å beregne avstand og kurs - det vil si retning - mellom spesifisert Posisjon gjenstander:

public class PositionUtility {public static double distance (Position position1, Position position2) {// Beregn og returner avstanden mellom de angitte posisjonene. } offentlig statisk dobbel overskrift (Posisjonsposisjon1, Posisjonsposisjon2) {// Beregn og returner overskriften fra posisjon1 til posisjon2. }} 

Jeg utelater den faktiske implementeringskoden for avstands- og kursberegninger.

Følgende kode representerer en typisk bruk av Posisjon og PositionUtility:

// Opprett en posisjon som representerer huset mitt Posisjon myHouse = ny posisjon (); myHouse.latitude = 36.538611; myHouse.longitude = -121.797500; // Opprett en posisjon som representerer en lokal kaffebar Position coffeeShop = ny posisjon (); coffeeShop.latitude = 36.539722; coffeeShop.longitude = -121.907222; // Bruk en PositionUtility til å beregne avstand og kurs fra huset mitt // til den lokale kaffebaren. dobbel avstand = PositionUtility.distance (myHouse, coffeeShop); dobbel overskrift = PositionUtility.heading (myHouse, coffeeShop); // Skriv ut resultater System.out.println ("Fra huset mitt på (" + myHouse.latitude + "," + myHouse.longitude + ") til kaffebaren på (" + coffeeShop.latitude + "," + coffeeShop. lengdegrad + ") er en avstand på" + avstand + "i retning" + retning + "grader."); 

Koden genererer utdataene nedenfor, noe som indikerer at kaffebaren er rett vest (270,8 grader) av huset mitt i en avstand på 6,09. Senere diskusjon adresserer mangelen på avstandsenheter.

 ===================================================== ================= Fra huset mitt på (36.538611, -121.7975) til kaffebaren på (36.539722, -121.907222) er avstand 6.0873776351893385 i en overskrift på 270.7547022304523 grader. ===================================================== ================== 

Posisjon, PositionUtility, og kodenes bruk er litt foruroligende og absolutt ikke veldig objektorientert. Men hvordan kan det være? Java er et objektorientert språk, og koden bruker objekter!

Selv om koden kan bruke Java-objekter, gjør den det på en måte som minner om en svunnen tid: verktøyfunksjoner som opererer på datastrukturer. Velkommen til 1972! Da president Nixon krøllet sammen over hemmelige båndopptak, brukte datapersonell som kodet på prosessspråket Fortran begeistret det nye International Mathematics and Statistics Library (IMSL) på akkurat denne måten. Kodelager som IMSL var fylt med funksjoner for numeriske beregninger. Brukerne sendte data til disse funksjonene i lange parameterlister, som til tider inkluderte ikke bare inngangen, men også utdatastrukturen. (IMSL har fortsatt å utvikle seg gjennom årene, og en versjon er nå tilgjengelig for Java-utviklere.)

I dagens design, Posisjon er en enkel datastruktur og PositionUtility er et IMSL-stil arkiv med biblioteksfunksjoner som fungerer Posisjon data. Som eksemplet ovenfor viser, utelukker moderne objektorienterte språk ikke nødvendigvis bruk av antikke prosessuelle teknikker.

Samle data og metoder

Koden kan enkelt forbedres. For det første, hvorfor plassere data og funksjonene som fungerer på disse dataene i separate moduler? Java-klasser tillater å samle data og metoder sammen:

public class Position {public double distance (Position position) {// Beregn og returner avstanden fra dette objektet til den angitte // posisjonen. } offentlig dobbel overskrift (Posisjonsposisjon) {// Beregn og returner overskriften fra dette objektet til den angitte // posisjonen. } offentlig dobbel breddegrad; offentlig dobbel lengdegrad; } 

Å plassere posisjonsdataelementene og implementeringskoden for beregning av avstand og kurs i samme klasse, fjerner behovet for en separat PositionUtility klasse. Nå Posisjon begynner å ligne en sann objektorientert klasse. Følgende kode bruker denne nye versjonen som samler data og metoder sammen:

Posisjon myHouse = ny posisjon (); myHouse.latitude = 36.538611; myHouse.longitude = -121.797500; Posisjon coffeeShop = ny posisjon (); coffeeShop.latitude = 36.539722; coffeeShop.longitude = -121.907222; dobbel avstand = myHouse.distance (coffeeShop); dobbel overskrift = myHouse.heading (coffeeShop); System.out.println ("Fra huset mitt på (" + myHouse.latitude + "," + myHouse.longitude + ") til kaffebaren på (" + coffeeShop.latitude + "," + coffeeShop.longitude + ") er en avstand på "+ avstand +" ved en overskrift på "+ overskrift +" grader. "); 

Utgangen er identisk som før, og enda viktigere, ovennevnte kode virker mer naturlig. Den forrige versjonen passerte to Posisjon motsetter seg en funksjon i en egen verktøyklasse for å beregne avstand og kurs. I den koden beregner du overskriften med metodeanropet util.heading (myHouse, coffeeShop) indikerte ikke beregningens retning. En utvikler må huske at verktøyfunksjonen beregner overskriften fra den første parameteren til den andre.

Til sammenligning bruker ovennevnte kode utsagnet myHouse.heading (coffeeShop) for å beregne samme overskrift. Anropets semantikk indikerer tydelig at retningen fortsetter fra huset mitt til kaffebaren. Konvertering av toargumentfunksjonen overskrift (posisjon, posisjon) til en en-argument-funksjon posisjon.overskrift (posisjon) er kjent som karri funksjonen. Currying spesialiserer effektivt funksjonen på sitt første argument, noe som resulterer i klarere semantikk.

Plasser metodene ved hjelp av Posisjon klassedata i Posisjon klassen selv lager karriering av funksjonene avstand og overskrift mulig. Endring av samtalestrukturen til funksjonene på denne måten er en betydelig fordel i forhold til prosessuelle språk. Klasse Posisjon representerer nå en abstrakt datatype som innkapsler data og algoritmene som fungerer på disse dataene. Som en brukerdefinert type, Posisjon objekter er også førsteklasses borgere som nyter godt av fordelene med Java språktypesystem.

Språkanlegget som samler data med operasjonene som utfører disse dataene, er innkapsling. Merk at innkapsling verken garanterer databeskyttelse eller skjuling av informasjon. Innkapsling sikrer heller ikke en sammenhengende klassedesign. For å oppnå disse kvalitetsdesignattributtene krever teknikker utover innkapslingen som tilbys av språket. Som for tiden implementert, klasse Posisjon inneholder ikke overflødige eller ikke-relaterte data og metoder, men Posisjon avslører begge deler breddegrad og lengdegrad i rå form. Det tillater enhver klient i klassen Posisjon å direkte endre enten internt dataelement uten inngrep fra Posisjon. Det er klart at innkapsling ikke er nok.

Defensiv programmering

For å undersøke videre konsekvensene av å eksponere interne dataelementer, antar jeg at jeg bestemmer meg for å legge til litt defensiv programmering til Posisjon ved å begrense breddegrad og lengdegrad til områder spesifisert av GPS. Breddegrad faller i området [-90, 90] og lengdegrad i området (-180, 180]. Eksponeringen av dataelementene breddegrad og lengdegrad i PosisjonDen nåværende implementeringen gjør denne defensive programmeringen umulig.

Å lage attributter breddegrad og lengdegrad privat data medlemmer av klassen Posisjon og legge til enkle tilgangs- og mutatormetoder, også kalt getters og setter, gir et enkelt middel for å eksponere rådataelementer. I eksempelkoden nedenfor skjermer settermetodene de interne verdiene på riktig måte breddegrad og lengdegrad. I stedet for å kaste et unntak, spesifiserer jeg å utføre modularitmetikk på inngangsverdiene for å holde de interne verdiene innenfor spesifiserte områder. Hvis du for eksempel prøver å sette breddegraden til 181.0, får du en intern innstilling på -179.0 for breddegrad.

Følgende kode legger til getter- og settermetoder for tilgang til private data-medlemmer breddegrad og lengdegrad:

offentlig klasse Posisjon {offentlig posisjon (dobbel breddegrad, dobbel lengdegrad) {setLatitude (breddegrad); settLengdegrad (lengdegrad); } public void setLatitude (double latitude) {// Sikre -90 <= latitude <= 90 ved bruk av modulo-aritmetikk. // Koden vises ikke. // Sett deretter instansvariabel. dette.breddegrad = breddegrad; } public void setLongitude (dobbel lengdegrad) {// Sikre -180 <lengdegrad <= 180 ved bruk av modulo-aritmetikk. // Koden vises ikke. // Sett deretter instansvariabel. dette. lengdegrad = lengdegrad; } offentlig dobbel getLatitude () {return latitude; } offentlig dobbel getLongitude () {retur lengdegrad; } offentlig dobbel avstand (posisjonsposisjon) {// Beregn og returner avstanden fra dette objektet til den angitte // posisjonen. // Koden vises ikke. } offentlig dobbel overskrift (Posisjonsposisjon) {// Beregn og returner overskriften fra dette objektet til den angitte // posisjonen. } privat dobbel breddegrad; privat dobbelt lengdegrad; } 

Ved hjelp av ovennevnte versjon av Posisjon krever bare mindre endringer. Som en første endring, siden koden ovenfor spesifiserer en konstruktør som tar to dobbelt argumenter, er ikke standardkonstruktøren lenger tilgjengelig. Følgende eksempel bruker den nye konstruktøren, så vel som de nye getter-metodene. Utgangen forblir den samme som i det første eksemplet.

Posisjon myHouse = ny posisjon (36.538611, -121.797500); Posisjon coffeeShop = ny posisjon (36.539722, -121.907222); dobbel avstand = myHouse.distance (coffeeShop); dobbel overskrift = myHouse.heading (coffeeShop); System.out.println ("Fra huset mitt på (" + myHouse.getLatitude () + "," + myHouse.getLongitude () + ") til kaffebaren på (" + coffeeShop.getLatitude () + "," + coffeeShop.getLongitude () + ") er en avstand på" + avstand + "ved en overskrift på" + overskrift + "grader."); 

Velge å begrense akseptable verdier av breddegrad og lengdegrad gjennom setter-metoder er strengt tatt en designbeslutning. Innkapsling spiller ingen rolle. Innkapsling, som manifestert på Java-språket, garanterer ikke beskyttelse av interne data. Som utvikler er du fri til å avsløre det indre av klassen din. Likevel bør du begrense tilgang og modifisering av interne dataelementer ved bruk av getter- og settermetoder.

Isolere potensiell endring

Å beskytte interne data er bare en av mange som gjelder å kjøre designbeslutninger på toppen av språkinnkapsling. Isolasjon til endring er en annen. Endring av den interne strukturen til en klasse bør ikke, hvis det er mulig, påvirke klientklasser.

For eksempel bemerket jeg tidligere at avstandsberegningen i klassen Posisjon angav ikke enheter. For å være nyttig trenger den rapporterte avstanden på 6,09 fra huset mitt til kaffebaren helt klart en måleenhet. Jeg vet kanskje retningen jeg skal ta, men jeg vet ikke om jeg skal gå 6,09 meter, kjøre 6,09 miles eller fly 6,09 tusen kilometer.

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