Programmering

Indkapsling er ikke skjult information

Ord er glatte. Som Humpty Dumpty proklameret i Lewis Carrolls Gennem glas, "Når jeg bruger et ord, betyder det netop, hvad jeg vælger at betyde - hverken mere eller mindre." Bestemt den almindelige brug af ordene indkapsling og information gemmer sig synes at følge den logik. Forfattere skelner sjældent mellem de to og hævder ofte direkte, at de er de samme.

Gør det det sådan? Ikke for mig. Var det simpelthen et spørgsmål om ord, ville jeg ikke skrive et nyt ord om sagen. Men der er to forskellige begreber bag disse begreber, begreber dannet separat og bedst forstået separat.

Indkapsling refererer til bundtning af data med de metoder, der fungerer på disse data. Ofte misforstås denne definition således, at dataene på en eller anden måde er skjult. I Java kan du have indkapslede data, der slet ikke er skjult.

Skjulning af data er dog ikke det fulde omfang af skjult information. David Parnas introducerede først begrebet information, der skjulte sig omkring 1972. Han hævdede, at de primære kriterier for systemmodularisering skulle vedrøre skjulingen af ​​kritiske designbeslutninger. Han understregede at skjule "vanskelige designbeslutninger eller designbeslutninger, som sandsynligvis vil ændre sig." At skjule information på den måde isolerer klienter fra at kræve intim viden om designet for at bruge et modul og fra virkningerne af at ændre disse beslutninger.

I denne artikel undersøger jeg sondringen mellem indkapsling og information, der gemmer sig gennem udviklingen af ​​eksempelkode. Diskussionen viser, hvordan Java letter indkapsling og undersøger de negative konsekvenser af indkapsling uden at skjule data. Eksemplerne viser også, hvordan man forbedrer klassedesignet ved hjælp af princippet om skjult information.

Positionsklasse

Med en voksende bevidsthed om det trådløse Internets store potentiale forventer mange eksperter, at placeringsbaserede tjenester giver mulighed for den første trådløse morderapp. Til denne artikels prøvekode har jeg valgt en klasse, der repræsenterer den geografiske placering af et punkt på jordens overflade. Som domæneenhed hedder klassen Position, repræsenterer GPS-oplysninger (Global Position System). Et første klip på klassen ser så simpelt ud som:

offentlig klasse Position {offentlig dobbelt bredde; offentlig dobbelt længdegrad; } 

Klassen indeholder to dataelementer: GPS Breddegrad og længde. På nuværende tidspunkt Position er intet andet end en lille pose data. Ikke desto mindre, Position er en klasse, og Position objekter kan instantieres ved hjælp af klassen. For at udnytte disse objekter, klasse PositionUtility indeholder metoder til beregning af afstand og kurs - dvs. retning - mellem specificeret Position genstande:

offentlig klasse PositionUtility {offentlig statisk dobbeltafstand (Positionsposition1, Positionsposition2) {// Beregn og returner afstanden mellem de angivne positioner. } offentlig statisk dobbelt overskrift (Position position1, Position position2) {// Beregn og returner overskriften fra position1 til position2. }} 

Jeg udelader den faktiske implementeringskode til afstands- og kursberegningerne.

Den følgende kode repræsenterer en typisk brug af Position og PositionUtility:

// Opret en position, der repræsenterer mit hus Position myHouse = ny position (); myHouse.latitude = 36.538611; myHouse.longitude = -121.797500; // Opret en position, der repræsenterer en lokal kaffebar Position coffeeShop = ny position (); coffeeShop.latitude = 36.539722; coffeeShop.longitude = -121.907222; // Brug en PositionUtility til at beregne afstand og kurs fra mit hus // til den lokale kaffebar. dobbeltafstand = PositionUtility.distance (myHouse, coffeeShop); dobbelt overskrift = PositionUtility.heading (myHouse, coffeeShop); // Udskriv resultater System.out.println ("Fra mit hus på (" + myHouse.latitude + "," + myHouse.longitude + ") til kaffebaren på (" + coffeeShop.latitude + "," + coffeeShop. længdegrad + ") er en afstand på" + afstand + "ved en overskrift på" + overskrift + "grader."); 

Koden genererer output nedenfor, hvilket indikerer, at caféen er ret vest (270,8 grader) for mit hus i en afstand af 6,09. Senere diskussion behandler manglen på afstandsenheder.

 ===================================================== ================= Fra mit hus på (36.538611, -121.7975) til kaffebaren ved (36.539722, -121.907222) er afstanden 6.0873776351893385 i en overskrift på 270.7547022304523 grader. ===================================================== ================== 

Position, PositionUtility, og deres kodebrug er lidt foruroligende og bestemt ikke meget objektorienteret. Men hvordan kan det være? Java er et objektorienteret sprog, og koden bruger objekter!

Selvom koden muligvis bruger Java-objekter, gør den det på en måde, der minder om en svunden æra: hjælpefunktioner, der fungerer på datastrukturer. Velkommen til 1972! Da præsident Nixon kramede sig over hemmelige båndoptagelser, brugte computerprofessionelle, der kodede på det processprog, Fortran begejstret det nye internationale matematik- og statistikbibliotek (IMSL) på netop denne måde. Kodelagre som IMSL var fyldt med funktioner til numeriske beregninger. Brugere sendte data til disse funktioner i lange parameterlister, som til tider ikke kun omfattede input, men også outputdatastrukturer. (IMSL har fortsat med at udvikle sig gennem årene, og en version er nu tilgængelig for Java-udviklere.)

I det nuværende design, Position er en simpel datastruktur og PositionUtility er et IMSL-stil lager af biblioteksfunktioner, der fungerer Position data. Som eksemplet ovenfor viser, udelukker moderne objektorienterede sprog ikke nødvendigvis brugen af ​​forældede proceduremæssige teknikker.

Bundtering af data og metoder

Koden kan let forbedres. Til at begynde med, hvorfor placere data og de funktioner, der fungerer på disse data, i separate moduler? Java-klasser tillader bundtning af data og metoder sammen:

public class Position {public double distance (Position position) {// Beregn og returner afstanden fra dette objekt til den angivne // position. } offentlig dobbelt overskrift (Positionsposition) {// Beregn og returner overskriften fra dette objekt til den angivne // position. } offentlig dobbelt bredde; offentlig dobbelt længdegrad; } 

At placere positionsdataelementerne og implementeringskoden til beregning af afstand og kurs i samme klasse undgår behovet for en separat PositionUtility klasse. Nu Position begynder at ligne en ægte objektorienteret klasse. Følgende kode bruger denne nye version, der samler data og metoder sammen:

Position myHouse = ny position (); myHouse.latitude = 36.538611; myHouse.longitude = -121.797500; Position coffeeShop = ny position (); coffeeShop.latitude = 36.539722; coffeeShop.longitude = -121.907222; dobbelt afstand = myHouse.distance (coffeeShop); dobbelt overskrift = myHouse.heading (coffeeShop); System.out.println ("Fra mit hus på (" + myHouse.latitude + "," + myHouse.longitude + ") til kaffebaren på (" + coffeeShop.latitude + "," + coffeeShop.longitude + ") er en afstand på "+ afstand +" i en overskrift på "+ overskrift +" grader. "); 

Outputtet er identisk som før, og vigtigere, ovenstående kode virker mere naturlig. Den tidligere version bestod to Position modsætter sig en funktion i en separat hjælpeklasse for at beregne afstand og kurs. I denne kode beregnes overskriften med metodeopkaldet util.heading (myHouse, coffeeShop) angav ikke klart beregningens retning. En udvikler skal huske, at hjælpefunktionen beregner overskriften fra den første parameter til den anden.

Til sammenligning bruger ovenstående kode udsagnet myHouse.heading (coffeeShop) at beregne den samme overskrift. Opkaldets semantik indikerer tydeligt, at retningen fortsætter fra mit hus til kaffebaren. Konvertering af to-argument funktion overskrift (Position, Position) til en funktion med et argument position.heading (Position) er kendt som karry funktionen. Currying har effektivt specialiseret funktionen på sit første argument, hvilket resulterer i klarere semantik.

Placering af metoderne ved hjælp af Position klassedata i Position Klassen selv gør currying funktionerne afstand og overskrift muligt. Ændring af opkaldsstrukturen for funktionerne på denne måde er en væsentlig fordel i forhold til proceduremæssige sprog. Klasse Position repræsenterer nu en abstrakt datatype, der indkapsler data og de algoritmer, der fungerer på disse data. Som en brugerdefineret type, Position objekter er også førsteklasses borgere, der nyder alle fordelene ved Java-sprogtypesystemet.

Sprogfaciliteten, der bundter data med de operationer, der udfører disse data, er indkapsling. Bemærk, at indkapsling hverken garanterer databeskyttelse eller skjult information. Kapsling sikrer heller ikke et sammenhængende klassedesign. For at opnå disse kvalitetsdesignattributter kræver teknikker ud over indkapslingen, der leveres af sproget. Som aktuelt implementeret, klasse Position indeholder ikke overflødige eller ikke-relaterede data og metoder, men Position udsætter begge dele Breddegrad og længde i rå form. Det tillader enhver klient i klassen Position for direkte at ændre enten internt dataelement uden indblanding fra Position. Det er klart, indkapsling er ikke nok.

Defensiv programmering

For yderligere at undersøge konsekvenserne af at eksponere interne dataelementer, antager jeg at jeg beslutter at tilføje lidt defensiv programmering til Position ved at begrænse bredde- og længdegrad til områder, der er specificeret af GPS. Breddegrad falder i området [-90, 90] og længdegrad i området (-180, 180]. Eksponeringen af ​​dataelementerne Breddegrad og længde i PositionDen nuværende implementering gør denne defensive programmering umulig.

At lave attributter breddegrad og længdegrad privat data medlemmer af klassen Position og tilføjelse af enkle accessor- og mutatormetoder, også almindeligvis kaldet getters og settere, giver et simpelt middel til at udsætte rådataposter. I nedenstående eksempelkode screener settermetoderne de interne værdier af Breddegrad og længde. I stedet for at kaste en undtagelse angiver jeg at udføre modularitmetik på inputværdier for at holde de interne værdier inden for specificerede områder. For eksempel forsøger man at indstille breddegraden til 181,0 i en intern indstilling på -179,0 for Breddegrad.

Den følgende kode tilføjer getter- og settermetoder til at få adgang til de private datamedlemmer Breddegrad og længde:

offentlig klasse Position {offentlig position (dobbelt bredde, dobbelt længde) {setLatitude (bredde); setLængdegrad (længdegrad); } public void setLatitude (double latitude) {// Sørg for -90 <= latitude <= 90 ved hjælp af modulo aritmetik. // Kode ikke vist. // Indstil derefter instansvariabel. denne.breddegrad = breddegrad; } public void setLongitude (dobbelt længdegrad) {// Sørg for -180 <længdegrad <= 180 ved hjælp af modulo aritmetik. // Kode ikke vist. // Indstil derefter instansvariabel. dette. længde = længdegrad; } offentlig dobbelt getLatitude () {return latitude; } offentlig dobbelt getLongitude () {return longitude; } offentlig dobbeltafstand (Positionsposition) {// Beregn og returner afstanden fra dette objekt til den angivne // position. // Kode ikke vist. } offentlig dobbelt overskrift (Positionsposition) {// Beregn og returner overskriften fra dette objekt til den angivne // position. } privat dobbelt bredde; privat dobbelt længde; } 

Brug af ovenstående version af Position kræver kun mindre ændringer. Som en første ændring, da ovenstående kode angiver en konstruktør, der tager to dobbelt argumenter, er standardkonstruktøren ikke længere tilgængelig. I det følgende eksempel bruges den nye konstruktør såvel som de nye getter-metoder. Outputtet forbliver det samme som i det første eksempel.

Position myHouse = ny position (36.538611, -121.797500); Position kaffeShop = ny position (36.539722, -121.907222); dobbelt afstand = myHouse.distance (coffeeShop); dobbelt overskrift = myHouse.heading (coffeeShop); System.out.println ("Fra mit hus på (" + myHouse.getLatitude () + "," + myHouse.getLongitude () + ") til kaffebaren på (" + coffeeShop.getLatitude () + "," + coffeeShop.getLongitude () + ") er en afstand på" + distance + "i en overskrift på" + overskrift + "grader."); 

Valg af at begrænse de acceptable værdier af Breddegrad og længde gennem settermetoder er strengt taget en designbeslutning. Indkapsling spiller ikke en rolle. Indkapsling, som det manifesteres på Java-sproget, garanterer ikke beskyttelse af interne data. Som udvikler er du fri til at udsætte internt i din klasse. Ikke desto mindre bør du begrænse adgangen til og ændringen af ​​interne dataelementer ved hjælp af getter- og settermetoder.

Isolering af potentiel ændring

Beskyttelse af interne data er kun en af ​​mange, der vedrører at køre designbeslutninger oven på sprogindkapsling. Isolering til forandring er en anden. Ændring af den interne struktur i en klasse bør ikke, hvis det overhovedet er muligt, påvirke klientklasser.

For eksempel bemærkede jeg tidligere, at afstandsberegningen i klassen Position angav ikke enheder. For at være nyttig har den rapporterede afstand på 6,09 fra mit hus til kaffebaren helt klart brug for en måleenhed. Jeg kender måske retningen, men jeg ved ikke, om jeg skal gå 6,09 meter, køre 6,09 miles eller flyve 6,09 tusind kilometer.

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