Versjon 3 av sjakk med JavaFX og FXML

I denne versjonen er hovedmålet å flytte mest mulig av sjakklogikken ut av kontrolleren, slik at det eneste som er igjen der er logikken for koblingen mellom GUI-et og sjakklogikken. Sjakklogikken havner i en såkalt domeneklasse som vi kaller Chess (domenet her er sjakk), som skal være fri for GUI-logikk. Et tydelig skille mellom domenelogikk og GUI gjør app-koden mye ryddigere og gir større mulighet for gjenbruk av domenelogikken i andre systemer.

Chess- og Piece-klassene

Formålet med domeneklassene Chess- og Piece, er å samle all logikk knyttet til spillet, samtidig som de er uavhengige av hvordan GUI-et er utformet. Data skal være innkapslet, slik at kontrolleren ikke kan gjøre noe galt. Chess-metodene kan utformes slik at det letter skriving av kontrolleren, så lenge Chess-klassen ikke blir stor og uryddig.

Logikken som ikke skal være i kontrolleren, er alt som har å gjøre med representasjon av brikkene og sjakkreglene. Det som skal være igjen er logikk knyttet til den visuelle representasjonen og tolkning av bruker-input. Den visuelle representasjon håndteres av updateBoard-metoden, som er grei som den er. Tolkning av bruker-input gjøres av handleMove-metoden, men den inneholder også en god del logikk om gyldige flytt som ikke skal være i kontrolleren. Som nevnt er Piece-klassen en domeneklasse, og ser man etter så inneholder den data som handler om visuell representasjon, nemlig unicode-symbolet. Logikken for sammenhengen mellom brikketype og -farge og unicode-symbolet må flyttes til kontrolleren, siden det regnes som UI-logikk.

Innkapsling av Piece-klassen

Vi kapsler først inn Piece, noe som først og fremst handler om å bruke private- og public-modifikatorene og å legge til metoder for uthenting og endring av data. I samme slengen endrer vi navngiving fra x, y til column og row.

Feltene white, kind, column og row gjøres private og får en tilsvarende get/is-metode. For symmetriens skyld lager vi også en isBlack-metode. symbol-feltet fjernes, fordi det er spesifikt for hvordan GUI-et bruker en Label med et unicode-symbol for visuell representasjon. Konstruktøren kan ikke lenger bruke unicode-tegnet for å bestemme brikke-fargen, så den legges til som (første) parameter. For å gjøre det lettere å oppgi et lovlig tegn for kind-argumentet, så lager vi én char-konstant, med public final static-modifikatorene, for hver brikketype. Bare column- og row-feltene skal kunne endres, og vi velger å lage en moveTo-metode som endrer dem samlet, heller enn å lage to set-metoder. En statisk checkTo-metode validerer at et kolonne-rad-par er gyldig. Merk at selv om moveTo-metoden validerer at argumentene er innenfor brettet vha. checkTo, så sjekker den ikke at kallet representerer et gyldig flytt, det må sjekkes i kontekst av hele brettet, typisk av kode i Chess-klassen.

Chess-klassen

Chess-klassen inneholder logikken for representasjon og initialisering av brettet og flytting av brikker, inkludert sjekking av regler for lovlige flytt. Brettet er som før, representert av en liste med brikker, som initialiseres i konstruktøren basert på en hjelpeliste med String-objekter. Hver String inneholder informasjon tilsvarende det konstruktøren i Piece tar inn. Chess sin move-metode har tatt over mye av logikken til kontrollerens handleMove. Merk at den returnerer en String som angir om noe gitt galt, og at den nå tar inn et reallyDo-argument, som sier om flyttet skal gjennomføres, slik at metoden kan kalles for kun å sjekke gyldigheten av et potensielt flytt. Hjelpemetoden for å sjekke reglene for lovlige flytt, checkMove endret til å returnere en String med tekst som sier hva som var galt med et flytt (eller null for at flyttet er greit), i stedet for false for å indikere at noe er galt. Det gjør at koden får en litt mer komplisert if-else-struktur, men det er verd det siden feilmeldingene til brukeren blir mer presise. Omskrivingen av den tidligere koden var dessuten nokså mekanisk, og tok ikke mye tid.

Kontrolleren

Den største endringen i kontrolleren er hvordan handleMove nå er endret, siden den med et internt Chess-objekt kan overlate mesteparten av jobben til ved å kalle dennes move-metode. Returverdien, når den ikke er null, brukes som melding til brukeren. Vi tar også høyde for at det utløses et unntak ved å bruke try/catch for å fange det opp. Det er egentlig ikke god skikk å vise frem en unntaksmelding direkte til brukeren, men sånn er det kodet her i alle fall. En annen endring er håndtering av unicode-tegnet for brikkene. Siden vi nå ikke lagrer det i Piece-objektene, må vi finne en annen måte å "beregne" det basert på brikketypen og -fargen. Til det bruker vi to Map-objekter, som fungerer som oppslagstabeller fra brikketypen til unicode-tegn for de to brikkefargene svart og hvit. Hvis en brikke er svart, slår vi altså opp i den ene tabellen, er den hvit slår vi opp i den andre.

Map-objektene initialiseres i konstruktøren basert på en String som inneholder en sekvens av brikketyper og tilhørende unicode-symboler for svarte og hvite brikker. Selve oppslaget gjøres av getPieceSymbol-metoden, som fungerer som en intern hjelpemetode. Dette er en form for intern innkapsling, hjelpemetoden gjør koden som bruker den uavhengig av de to Map-variablene. Hvis vi velger å representere sammenhengen mellom brikketype og -farge på en annen måte, trenger vi bare å endre hjelpemetoden, ikke koden som bruker den.

Sammenligning av v2 og v3

Det er instruktivt å sammenligne klassediagrammene fra versjon 2 og versjon 3, som er vist under. De er redigert noe, for å få frem poenget, som er at det stort sett handler om flytting av logikk. Koden er omtrent den samme, den er fordelt på omtrent de samme metodene, men en del av metodene er flyttet fra ChessController til Chess.

diag 0e29557861b8b424cd8bc09b1e9bac29

ChessController-klassen har fortsatt metodene initialize, updateBoard og handleMove, mens det som før var i konstruktøren er flyttet over i Chess sin konstruktør, som kalles i initialiseringen av ChessController sin chess-variabel. En del av logikken i handleMove er dessuten overført til Chess sin move-metode. De andre metodene i ChessController er flyttet til Chess, isValidMove ble riktignok endret litt og gitt navnet checkMove, men logikken er den samme.