Användargränssnitt till objektorienterade system

Idéerna på den här sidan bygger huvudsakligen på en artikelserie ur JavaWorld. Första avsnittet finns här, http://www.javaworld.com/javaworld/jw-07-1999/jw-07-toolbox.html, därifrån finns länkar till övriga avsnitt.

OOD i allmänhet

Den viktigaste principen inom OOD är inte arv utan delegering. Det vill säga operationer på vissa data ska delegeras till det objekt som hanterar datat, hämta inte datat från objektet för att sedan utföra operationen på något annat ställe. Om klassdiagrammet ser ut som ett trassligt garnnystan är det definitivt inte bra, ett objekt ska ha så få beroenden av andra objekt som det någonsin är möjligt. Däremot ska de metoder ett objekt tillhandahåller alla ha en klar koppling till det koncept objektet representerar, och till objektets data. Ett objekt är sina metoder, vad omärlden anbelangar. Det är endast metoderna som är åtkomliga utifrån, hur objektets data är representerat internt i objektet, eller om det över huvud taget är representerat där, har ingen annan med att göra.

Problemet med OOD av ett användargränssnitt

Så långt är säkert alla överens, nu över till artikelförfattarens (Allen Holub) åsikter om hur detta tillämpas på designen av ett användargränssnitt. När användargränssnittet till ett objekt ska konstrueras uppstår följande problem med ovanstående principer: Om ingen kod utanför objektet ska vara beroende av objektets interna konstruktion går det inte att fråga objektet om dess data och sedan visa dem på skärmen. Gick det vore vi tvugna att låta den kod som hanterade skärmen både läsa och skriva objektets interna variabler. För att undvika detta och låta objektet behålla sitt inre för sig själv måste det kunna exponera sig själv på skärmen, innehålla sitt eget användargränssnitt.

Även denna tanke ställer dock till problem. Det finns en mycket stor risk att kod som har med bildskärmshantering att göra blir insprängd i en hopplös röra lite överallt i objektets metoder. Tanken bryter dessutom mot en annan viktig princip, nämligen att modellen (den logiska representationen av programmets tillstånd och operationerna på den) ska vara skiljd från användargränssnittet.

En tredje tanke vore att skicka objektet en referens till en frame där det skulle kunna rita sig, men varje objekt ska knappast ha en egen frame och hur ska objekt som inte känner till varandra kunna samsas om samma frame?

Lösningen på problemet: visual proxy

Holubs lösning bygger på mönstret PAC som består av de tre delarna Presentation, Abstraction och Controller. Abstraction är i vårt fall själva objektet vars data ska visas på skärmen. Det är ansvarigt för att kunna skapa en presentation, till exempel en JComponent, som innehåller den visuella representationen av objektets data. Det är denna hankallar visual proxy. Controller kan till exempel vara en frame. Controllern anropar alla abstrction som ska visas och begär deras presentation. Sedan sammanställer den alla presentation i framen och visar den på skärmen. Nu finns objektets data på skärmen (i dess presentation) utan att någon utomstående kod känner till någonting om det och utan att objektet känner till någonting om skärmen! Inte illa!!

Det som återstår är hur presentation, abstraction och controller kommunicerar med varandra. Lösningen är som följer: Alla abstraction, det vill säga alla objekt som ska visas, implementerar ett gränssnitt. Det kan heta till exempel Showable. En controller har en förteckning över alla Showable den ska kunna visa, den behöver dock (och ska inte heller) veta vad det egentligen handlar om för klasser. Den refererar dem bara som Showable. Showable innehåller en enda metod, låt oss kalla den getVisualComponent(), vilken returnerar presentation, till exempel i form av en JComponent. När en controller ska visa sina abstraction på skärmen anropar den getVisualComponent() i allihopa och samlar ihop deras presentation-JComponent:ar varefter den visar dem på skärmen i sin frame. Alla abstrction har registrerat sig som händelselyssnare till sina respektive presentation och får på så sätt reda på om användaren uppdaterar dess data. Om en abstraction slutligen måste uppdatera sin presentation medan den syns på skärmen lägger den in en uppdateringsbegäran på Swings händelsekö med SwingUtils.invokeLater().

En controller har alltså referenser till samtliga abstraction, dock endast i form av Showable, och erhåller därifrån referenser till deras presentation. En abstraction har endast en referens till sin presentation när den finns på skärmen. En presentation, slutligen, har inte referens till någon annan.

Klass- och sekvensdiagram för "visual proxy".

Ovanstående är en extremt förenklad variant av den design som beskrivs i artikelserien. För att till fullo förstå och kunna utnyttja detta mönster är det förmodligen nödvändigt att läsa åtminstone de fyra första avsnitten.

Två vanliga frågor ska dock besvaras redan här:

  1. Hur ska en Presentation kunna visa en meny?
    Controller får implementera ett interface (i artiklarna kallas det MenuSite) som innehåller metoder för att hantera menyer. När en Presentation ska visas på skärmen kan den hitta den MenuSite den ligger i med metoden SwingUtilites.getAncestorOfClass(MenuSite.class, this). Sedan kan den lägga till och ta bort menyer enligt önskan. För att detta ska fungera måste Presentation veta när den visas eftersom det är först då getAncestorOfClass() kan användas. Det kan den få reda på till exempel genom att lyssna efter ComponentEvent från sig själv.
  2. Hur ska en Abstraction kunna få en annan att representeras på skärmen?
    Antag till exempel att en klass i modellen (dvs en Abstraction) representerar en avdelning på ett företag och att en annan representerar en anställd. Antag vidare att Presentation för avdelningen finns på skärmen och att användaren utför någon åtgärd som ska resultera i att information om en av avdelningens anställda visas. För att åstadkomma detta måste den Abstraction som representerar avdelningen beordra den som representerar den anställda att rita sig på skärmen. Så långt är allt väl, men hur ska denna senare Abstraction kunna rita upp sig om den inte har någon referens till något fönster på skärmen? Denna fråga besvaras i uppgift tre av artikelserien men jag förstår faktiskt inte svaret. Såvitt jag kan se det finns det två alternativ. Antingen utökas klassdiagrammet ovan så att även Controller implementerar något interface vilket innehåller en metod som tar en JPanel och visar den på skärmen. Abstraction måste då ha en referens till Controller och anropa nyss nämnda metod när den vill visa sig. Det andra alternativet är att alla Abstraction alltid har en Presentation hos sin Controller (även om den ibland inte visas på skärmen). Abstraction för den anställde kan då visa sig på skärmen genom att rita i sin Presentation utan att ha någon som helst kunskap om någon Controller.