Vi har diskuterat objektorienterad programmering för spelutvecklare i allmänhet och de specifika OOP-principerna för sammanhållning och koppling. Låt oss nu ta en titt på inkapsling och hur det hjälper till att hålla koden löst kopplad och mer underhållbar.
Notera: Även om denna Snabba Tips förklaras med Java, borde du kunna använda samma tekniker och begrepp i nästan vilken spelutvecklingsmiljö som helst.
Inkapsling är principen om att information gömmer sig. Det är genomförande (det interna arbetet) av ett objekt är dolt från resten av programmet.
Ett populärt exempel du hör om inkapsling kör en bil. Behöver du veta exakt hur varje aspekt av en bil fungerar (motor, karburator, generator, etc.)? Nej - du behöver veta hur man använder ratten, bromsarna, gaspedalen och så vidare.
Ett annat exempel söker efter ett värde i en array. I Java kan du göra följande:
int myArray [] = 1, 2, 3, 5, 7, 9, 11, 13; Arrays.asList (myArray) .contains (11);
Ovanstående kod kommer att återvända Sann
om värdet 11
är i myArray
, annars kommer det att återvända falsk
. Hur gör det innehåller ()
metod arbete? Vilken sökteknik använder den? Pre-sorterar arrayen innan du söker? Svaret är det spelar ingen roll eftersom det exakta genomförandet av metoden är gömt.
Encapsulation hjälper till att skapa kod som är löst kopplad. Eftersom detaljerna är gömda, reducerar det möjligheten för andra objekt att direkt ändra ett objekts tillstånd och beteende.
Det hjälper också mycket när du måste ändra datatypen för en variabel. Låt oss säga att du bestämde dig för att använda a Sträng
för att hålla koll på tiden i formatet "hh: mm: ss". Efter en stund kommer du att inse att en int
representerande sekunder kan vara en bättre datatyp för tiden. Inte bara måste du ändra datatypen i objektet, men också varje gång du refererade objektets tid i hela programmet!
I stället kan du använda det som kallas getter och setterfunktioner. Getters och setters är vanligtvis små funktioner som returnerar och ställer in en variabel. En getter-funktion för att få tiden skulle se ut som följer:
public String getTime () returtid;
Getter kommer att returnera a Sträng
värde: variabeln tid
. Nu när vi vill förändras tid
till en int, istället för att ändra alla samtal till getter kan vi bara ändra getterfunktionen för att ändra int
datatyp i a Sträng
data typ.
public String getTime () // dela upp tiden i timmar, minuter och sekunder int timmar = tid / 3600; int återstående tid = tid% 3600; int minuter = rest / 60; int sekunder = rest% 60; // // kombinera resultatet till en kolonavskiljad strängåtergångstid + ":" + minuter + ":" + sekunder;De
%
Operatören är känd som moduloperatören och returnerar resten av en division. Så 10% 3
skulle återvända 1
eftersom 10/3 = 3
med en återstående del av 1
. Getter-funktionen returnerar nu a Sträng
och ingen av samtalen till funktionen måste ändras. Koden är därför mer underhållbar.
Låt oss gå tillbaka till exemplet på ett fartyg som avfyrar en kula som introduceras i kopplingsartikeln. Minns att vi definierade ett skepp enligt följande:
/ ** * Skeppsklassen * / allmänklassen Skepp / ** * Funktion - utför beteendet (uppgift) för att vrida skeppet * / public void rotate () // Kod som vänder skeppet / ** * Funktion - utför uppförandet (uppgift) för att flytta Ship * / public void move () // Kod som rör fartyget / ** * Funktion - utför uppträdandet (uppgift) för att skjuta fartygets pistol * / allmän tomrumsbrand ) // Kod som gör fartyget en kula
Att få skeppet att elda en kula, allt du behöver göra är att ringa ship.fire ()
. Hur koden implementerar att skjuta en kula är inte viktigt, för allt vi bryr oss om skjuter en kula. På det här sättet, om du vill byta kod för att avfyra en laserblast istället behöver du bara byta metod brand()
och inte alla samtal till ship.fire ()
.
Detta gör det möjligt för dig att kunna ändra vilken aspekt av hur fartyget fungerar utan att behöva ändra hur fartygsobjektet används överallt.
Tetris har några olika sätt att hantera clearinglinjer. Inkapsling av uppförandet av att rensa en rad gör att du snabbt kan ändra vilken metod du använder utan att behöva skriva om varje användning av den. Detta kommer till och med göra det möjligt för dig att kunna ändra den klara linjemetoden för att skapa olika lägen i spelet.
Det finns ytterligare ett inslag av inkapsling som handlar om att gömma åtkomsten till ett objekt. Det finns många gånger där du inte vill ha extern tillgång till objektets tillstånd eller beteende, och vill så gömma det från något annat objekt. För att göra detta kan du använda åtkomstnivåmodifierare som ger eller döljer åtkomst till objektens tillstånd och beteenden.
Åtkomstnivåmodifierare definierar två åtkomstnivåer:
offentlig
- Alla objekt kan när som helst nå åtkomst till variabeln eller funktionen.privat
- endast objektet som innehåller variabeln eller funktionen kan komma åt den.(Det finns en tredje nivå - skyddad
- men vi kommer att täcka det i ett framtida inlägg.)
Minns att ett spöke hade tillstånd av:
Färg
namn
stat
riktning
fart
... och definierades enligt följande:
/ ** * Ghostklassen * / allmänklassen Ghost / ** * Function - flyttar Ghost * / public void move () // Kod som flyttar Ghost i nuvarande riktning / ** * Funktion - ändra Ghost direction * / public void changeDirection () // Kod som ändrar Ghosts riktning / ** * Funktion - ändra Ghost speed * / public void changeSpeed () // Kod som ändrar Ghosts hastighet / ** * Funktion - ändra Ghost color * / public void changeColor () // Kod som ändrar Ghosts färg / ** * Funktion - ändra Ghost state * / public void changeState () // Kod som ändrar Ghosts tillstånd // Denna funktion också kommer att ringa de tre funktionerna changeDirection (), changeSpeed () och changeColor ()
Eftersom ändra färg()
, changeSpeed ()
, och Byt riktning()
är hjälparfunktioner och ska inte nås från någon annanstans men inom klassen kan vi definiera dem som privat
funktioner. Alla spökens stater kan också deklareras privat
eftersom de inte bör ändras från utsidan av klassen, och endast åtkomliga genom getter och setterfunktioner. Detta skulle ändra klassen som definieras enligt följande:
/ ** * Ghostklassen * / offentlig klass Ghost // Ghost states private String color; privat strängnamn; privat Boolean isEatable; privat vektorriktning; privat int hastighet; / ** * Funktion - flyttar Ghost * / public void move () // Kod som flyttar spöket i nuvarande riktning / ** * Funktion - ändra Ghost direction * / privat void changeDirection () // Kod som ändrar Ghosts riktning / ** * Funktion - ändra Ghost speed * / privat void changeSpeed () // Kod som ändrar Ghosts hastighet / ** * Funktion - ändra Ghost color * / privat void changeColor () // Kod som ändrar Ghosts färg / ** * Funktion - ändra Ghost state * / public void changeState () // Kod som ändrar Ghosts tillstånd // Den här funktionen kommer också att ringa de tre funktionerna changeDirection, changeSpeed och changeColor / ** * Getters och setters * / ...
Inkapsling kan bidra till att skapa mer underhållbar kod genom att bidra till att förhindra rippel-effekten av kodändringar. Det hjälper också till att skapa löst kopplad kod genom att minska direktåtkomst till objektets tillstånd och beteende.
I nästa Quick Tip diskuterar vi principen om abstraktion och hur det kan bidra till att minska kodredundans. Följ oss på Twitter, Facebook eller Google+ för att hålla dig uppdaterad med de senaste inläggen.