C++-arkivet
|
Interfaceskolan del 2:Vi börjar med tipset:
Och en användbar observation om beroenden:
Olika beroendenVad är ett beroende? Kort kan man säga att X beror på Y om X måste ändras när Y ändras. Ett klassiskt beroende är mellan objektfil (t.ex. a.obj) och headerfil (t.ex. a.hpp). Ändrar du på headerfilen a.hpp (= Y) måste implementationsfilen a.cpp kompileras om och då skapas en ny objektfil a.obj (= X). Ett annat exempel är beroendet mellan ditt exekverbara program och dina objektfiler. Ändras din objektfil a.obj (= Y) måste du länka om ditt program för att skapa a.exe (= X).
När uppstår beroenden?Den typ av beroenden vi beskrev ovan uppstår när man gör #include. Som vi såg är beroenden någonting vi vill undvika. Ibland är vi dock tvungna att inkludera andra filer. Antag att vi har två klasser A och B i filerna a.hpp och b.hpp. Tabell 1 listar de tre situationer där a.hpp måste inkludera b.hpp:
I de fall som inte räknas upp i Tabell 1 behöver a.hpp inte göra #include på b.hpp! Istället lägger vi till en (framåt)deklaration av klassen B i a.hpp genom att skriva class B; (engelska: forward declare). Vi tar ett exempel: Antag att klassen A deklarerar en medlemsfunktion B bar(void), dvs en funktion som returnerar ett objekt av typen B. Det räcker då att vi deklarerar B! Funktionen bar implementerar vi sedan i a.cpp. Eftersom bar skapar en instans av B så måste vi ha tillgång till Bs definition. Därför måste a.cpp inkludera b.hpp. Vi har alltså fått bort beroendet från a.hpp till b.hpp. Men beroendet mellan klassen A och klassen B har inte försvunnit, det har bara flyttat sig. Nu är det istället a.cpp som beror på a.hpp. Det är i och för sig en förbättring eftersom vi förmodligen förkortar våra kompileringstider. Vissa typer av beroenden hindrar oss från att skriva bra kod. Och som vi såg tar framåtdeklarationen inte bort alla beroenden. Vill vi verkligen ha bort beroendet så måste vi ta till andra medel. Så hur gör vi? Vissa beroenden kan vi inte leva med!Först måste vi förstå varför det är så viktigt att kunna ta bort beroendet mellan två klasser. Det gör vi lättast med ett exempel. Låt säga att du vill skriva ett program som ska prata med andra program över internet över något protokoll (t.ex. HTML, SIP eller dyl.). Du vill att ditt program ska kunna skicka förfrågningar mha ditt protokoll och sedan ta emot svar. Du inser att du förmodligen vill skriva fler program i framtiden som pratar samma protokoll. Därför behöver du protokollkod som är skriven för att återanvändas. För enkelhetens skull låter vi ditt program representeras av en klass Application och din protokollkod av en klass Protocol. Applikationsklassen ber protokollklassen att skicka en förfrågan. Det tar sedan en stund tills svaret kommer. Under tiden applikationen väntar måste den kunna skicka fler förfrågningar (omsända gamla förfrågningar som inte fått något svar eller skicka nya till andra program). Du kan därför inte låta ditt program vara inaktivt tills det fått svar. Istället får protokollklassen kontakta applikationen när den fått svar! Protokollklassen ska alltså prata med applikationsklassen (t.ex. genom att anropa en funktion gotResponse). Vi får därmed ett beroende. Det spelar ingen roll om protocol.hpp gör #include på application.hpp eller om den deklarerar med class Application;. Protokollklassen är ändå låst till precis en applikationsklass som heter Application. Din kod går inte att återanvända på ett lätt sätt. Så vad göra? Och då äntligen till ämnet: interfaceklasser! Interfaceklasser bryter beroendetI den första delen i artikelserien om interfaceklasser pratade vi om vad ett interface var. Interfacet beskriver vad som ska göras, men inte hur det ska göras. I detta fall vet vi att någon vill ha ett anrop (engelska: callback) när ett svar kommer. Vi vill dock undvika att bestämma vem som ska ta emot anropet. Vi inför en interfaceklass ProtocolCallback:
Vi låter sedan applikationen ärva från ProtocolCallback:
Applikationen skickar sin förfrågan genom att anropa en funktion sendRequest i protokollklassen. För att protokollklassen ska veta vem som vill ha svaret måste vi hjälpa till. Vi skickar med ett argument av typen ProtocolCallback. Resultatet blir något i stil med:
När protokollklassen tar emot anropet till sendRequest sparar den undan en referens till klassen ProtocolCallback. I detta fall döljer sig ett objekt av typen Application bakom referensen. På så sätt blir protokollklassen helt ovetandes om applikationen och din kod kan återanvändas! Referensen används sedan för att hitta tillbaka till den som vill ha svaret. Dela upp ditt program i lagerMellan applikationsklassen och protokollklassen finns alltså bara beroenden i ena riktningen. På samma sätt kan man titta på beroenden mellan alla klasser i ditt program. Förhoppningsvis hittar du då klasser som beror på få eller inga andra klasser. De brukar ofta vara klasser som många andra delar av ditt program använder, såsom hjälpklasser för stränghantering eller felutskrift. Vi tänker oss dessa hjälpklasser som byggstenar som resten av ditt program vilar på. Ovanpå dessa klasser bygger vi nya hjälpklasser och längst upp lägger vi vår applikation. Observera att inga beroenden får finnas mellan en nivå av byggklossar och de nivåer som ligger ovanpå. Vi kallar dessa nivåer för lager (engelska: layers). Det understa lagret (lager 0) är helt fristående från resten av programmet. Därför går det även att återanvända om det är bra skrivet. Lager 1 ligger ovanpå lager 0 (och behöver lager 0 för att kompilera). Därmed kan lager 1 återanvändas, bara man har tillgång till lager 0. Det är nyttigt att tänka igenom alla dina klasser innan du skapar ditt program! Märker du t.ex. att alla klasser hamnar i samma lager (t.ex. för att alla klasser beror på varandra i en cirkel) bör du fundera på varför. Om t.ex. hjälpklasserna för felutskrifter beror på applikationen är det något som inte står rätt till. Kanske behöver du införa ett interface för att bryta beroendet! Observera att det inte är ovanligt med beroenden inom ett och samma lager, t.o.m. cirkulära beroenden.
Till sistDu har nu tagit bort alla beroenden från ditt protokoll till din applikation. Men det ska erkännas att din .exe-fil fortfarande beror på klassen Application. Det du har vunnit är att du kan byta ut Application mot något annat. I nästa artikel om interfaceklasser ska vi prata om varför det är så bra att kunna byta klasser. Artikeln handlar om hur du får bättre kvalitet på dina program genom bättre testbarhet! Hoppas att detta hjälper dig skriva bättre program i framtiden. Lycka till att bli av med dina beroenden! Relaterade artiklar:
|