Att utveckla en större applikation
Hittills i detta kursmaterial har vi gÄtt igenom ett stort antal Python-funktioner.
I Introduktion till programmering-kursen introducerades kontrollstrukturer som while och for, funktioner samt grundlÀggande datastrukturer som listor, tupler och ordlistor. I princip Àr dessa verktyg allt som behövs för att uttrycka vad som helst som en programmerare kan tÀnka sig vilja uttrycka med Python.
PÄ denna avancerade kurs i programmering, med början i modul 8 av materialet, har du blivit bekant med klasser och objekt. LÄt oss ta en stund att fundera pÄ nÀr och varför de Àr nödvÀndiga, ifall de grundlÀggande verktygen frÄn modul 1 till 7 borde rÀcka.
Att hantera komplexitet
Objekt och klasser Àr lÄngt ifrÄn nödvÀndiga i alla programmeringssammanhang. Om du t.ex. programmerar ett litet skript för engÄngsbruk Àr objekt oftast överflödiga. Men nÀr du ska programmera nÄgot större och mer komplicerat blir objekten mycket anvÀndbara.
NĂ€r programmen blir allt mer komplexa blir mĂ€ngden detaljer snabbt ohanterlig, sĂ„vida inte programmet Ă€r organiserat pĂ„ nĂ„got systematiskt sĂ€tt. Ăven nĂ„gra av de mer komplicerade övningarna pĂ„ den hĂ€r kursen hittills skulle ha haft nytta av de exempel som ges i den hĂ€r delen av materialet.
I flera decennier har begreppet Separation of concerns varit en av de centrala principerna inom programmering och inom datavetenskapen i stort. Citat frÄn Wikipedia:
Separation of concerns is a design principle for separating a computer program into distinct sections such that each section addresses a separate concern. A concern is a set of information that affects the code of a computer program
Genom att dela upp programmet i olika delar, sÄ att varje del har sina egna problem att hantera, blir det lÀttare att hantera den oundvikliga komplexiteten i ett datorprogram.
Funktioner Àr ett sÀtt att organisera ett program i distinkta, hanterbara helheter. I stÀllet för att skriva ett enda skript Àr tanken att formulera smÄ, separat verifierbara funktioner som var och en löser en del av det större problemet.
En annan vanlig metod för att hantera större program Àr objekt, genom objektorienterade programmeringsprinciper. Det finns fördelar och nackdelar med bÄda metoderna, och varje programmerare har sin egen favorit. Som vi har sett hittills kan vi med hjÀlp av objekt och klasser samla alla data och den kod som bearbetar dessa data i en enda enhet, i ett objekts attribut och metoder. Dessutom ger objekten ett sÀtt att kapsla in de data de kontrollerar, sÄ att andra delar av programmet inte behöver oroa sig för de interna detaljerna i ett objekt.
Ett fungerande exempel: telefonkatalog
Hur ska ett program delas in i klasser och objekt? Det hÀr Àr inte alls en enkel frÄga med ett enda godtagbart svar, sÄ vi fortsÀtter med ett exempel. I del fem genomförde du en telefonkatalogsapplikation, och nu ska vi genomföra nÄgot liknande med hjÀlp av objektorienterade programmeringsprinciper.
Enligt principen om separation of concerns bör ett program delas upp i sektioner som var och en har sin egen sak att ta hand om. I objektorienterad programmering översÀtts detta till principen om ett ansvar. Utan att gÄ in pÄ detaljerna framgÄr det grundlÀggande syftet redan av namnet: en enda klass och de objekt som skapas utifrÄn den ska ha ett enda ansvar i programmet.
Objektorienterad programmering anvÀnds ofta som ett sÀtt att modellera objekt och fenomen i den verkliga vÀrlden. Ett enskilt objekt i den verkliga vÀrlden modelleras med en enda klass i programkoden. I fallet med en telefonkatalog kan sÄdana objekt vara
- en person
- ett namn
- ett telefonnummer
Ett namn och ett telefonnummer kan uppfattas som data som inte förtjÀnar egna klasser, men en person Àr en distinkt fysisk enhet i den verkliga vÀrlden, och i programmeringsvÀrlden skulle den kunna fungera som en klass. Ett Person-objekt skulle vara ansvarigt för att knyta ihop ett namn och de telefonnummer som Àr kopplade till det.
En telefonkatalog i sig skulle kunna vara en bra kandidat för en klass. Dess ansvar skulle vara att hantera olika personobjekt och de data de innehÄller.
Nu har vi skissat kÀrnan i vÄr applikation: telefonkatalog och person utgör programmeringslogiken i vÄr applikation, eller applikationslogiken i korthet. VÄr applikation skulle behöva nÄgra andra klasser ocksÄ.
Det Àr oftast en bra idé att hÄlla all interaktion med en anvÀndare skild frÄn applikationslogiken. Det Àr ju trots allt ett helt eget ansvar. Förutom den centrala applikationslogiken bör vÄrt program dÀrför innehÄlla en klass som hanterar anvÀndargrÀnssnittet.
Dessutom bör vÄr telefonkatalog ha nÄgon form av bestÀndig lagring mellan exekveringar. Filhanteringen Àr Äterigen ett tydligt separat ansvar, sÄ den förtjÀnar en egen klass.
Nu nÀr vi har en översikt över de grundlÀggande komponenterna i vÄrt program uppstÄr frÄgan: var ska vi börja programmera? Inte heller hÀr finns det nÄgot rÀtt eller fel svar, men det Àr ofta en bra idé att börja med nÄgon del av programlogiken.
Steg 1: en skiss för applikationslogiken
LÄt oss börja med klassen Telefonkatalog. En skelettimplementering skulle kunna se ut sÄ hÀr:
class Telefonkatalog:
def __init__(self):
self.__personer = []
def tillsatt_nummer(self, namn: str, nummer: str):
pass
def hamta_nummer(self, namn: str):
pass
Denna klass bestÄr av en lista med personer samt metoder för att bÄde lÀgga till och hÀmta data.
Varje person kan vara kopplad till flera nummer, sĂ„ lĂ„t oss implementera den interna strukturen för personer med en ordlista. En ordlista ger oss möjlighet att söka efter nycklar enligt namn, och vĂ€rdet som Ă€r kopplat till en ordlistas nyckel kan vara en lista. Hittills ser det ut som att vi inte behöver en separat klass för att representera en person â ett inlĂ€gg i en ordlista rĂ€cker.
LÄt oss implementera de metoder som listas ovan och testa vÄr telefonkatalog:
class Telefonkatalog:
def __init__(self):
self.__personer = {}
def tillsatt_nummer(self, namn: str, nummer: str):
if not namn in self.__personer:
# till personen Àr en lista med nummer anknyten
self.__personer[namn] = []
self.__personer[namn].append(nummer)
def hamta_nummer(self, namn: str):
if not namn in self.__personer:
return None
return self.__personer[namn]
# testkod
katalog = Telefonkatalog()
katalog.tillsatt_nummer("Erik", "02-123456")
print(katalog.hamta_nummer("Erik"))
print(katalog.hamta_nummer("Emilia"))Detta borde utskriva följande:
['02-123456'] None
Metoden hamta_nummer returnerar None om ett namn inte finns med i telefonkatalogen. Om namnet finns returneras listan med de nummer som Àr kopplade till namnet.
NÀr man gör Àndringar i ett program Àr det alltid vÀrt att testa att koden fungerar som förvÀntat innan man gÄr vidare med andra Àndringar. Den kod som anvÀnds för testning Àr vanligtvis nÄgot som raderas strax efterÄt, och dÀrför kanske du tycker att det inte Àr vÀrt besvÀret att skriva nÄgra tester i första hand. I de flesta fall Àr detta inte sant. Testning Àr en förutsÀttning för bra programmeringsresultat.
En bugg i programmet bör fÄngas upp och ÄtgÀrdas sÄ snart som möjligt. Om du tar för vana att kontrollera funktionaliteten i praktiskt taget varje ny kodrad kommer du att upptÀcka att buggarna oftast Àr lÀtta att hitta och ÄtgÀrda, eftersom du kan vara helt sÀker pÄ att buggen orsakades av den senaste Àndringen. Om du bara testar programmet efter att ha lagt till dussintals rader kod ökar de potentiella kÀllorna till buggar ocksÄ med dussintals gÄnger.
Steg 2: en skiss för anvÀndargrÀnssnittet
Med den grundlÀggande applikationslogiken ur vÀgen Àr det dags att implementera ett textbaserat anvÀndargrÀnssnitt. Vi kommer att behöva en ny klass, TelefonkatalogApplikation, med följande inledande funktionalitet:
class TelefonkatalogApplikation:
def __init__(self):
self.__katalog = Telefonkatalog()
def hjalp(self):
print("instruktioner: ")
print("0 avsluta")
def exekvera(self):
self.hjalp()
while True:
print("")
instruktion = input("instruktion: ")
if instruktion == "0":
break
applikation = TelefonkatalogApplikation()
applikation.exekvera()Det hÀr programmet gör inte sÄ mycket Ànnu, men lÄt oss gÄ igenom innehÄllet. Konstruktormetoden skapar en ny Telefonkatalog, som lagras i ett privat attribut. Metoden exekvera(self) startar programmets textbaserade anvÀndargrÀnssnitt, vars kÀrna Àr while-loopen, som fortsÀtter att be anvÀndaren om instruktioner tills de skriver in instruktionen för att avsluta. Det finns ocksÄ en metod för instruktioner, hjalp(self), som anropas innan man gÄr in i loopen, sÄ att instruktionerna skrivs ut.
LÄt oss nu lÀgga till lite faktisk funktionalitet. Först implementerar vi att kunna lÀgga till nya data i telefonkatalogen:
class TelefonkatalogApplikation:
def __init__(self):
self.__katalog = Telefonkatalog()
def hjalp(self):
print("instruktioner: ")
print("0 avsluta")
print("1 tillsÀtt inlÀgg")
def exekvera(self):
self.hjalp()
while True:
print("")
instruktion = input("instruktion: ")
if instruktion == "0":
break
elif instruktion == "1":
namn = input("namn: ")
nummer = input("nummer: ")
self.__katalog.tillsatt_nummer(namn, nummer)
applikation = TelefonkatalogApplikation()
applikation.exekvera()Om anvÀndaren skriver in 1 för att lÀgga till ett nytt nummer, frÄgar anvÀndargrÀnssnittet efter ett namn och ett nummer och lÀgger till dessa i telefonkatalogen med hjÀlp av den lÀmpliga metod som definieras i klassen.
AnvÀndargrÀnssnittets enda ansvar Àr att kommunicera med anvÀndaren. All annan funktionalitet, t.ex. att lagra ett nytt namn- och nummerpar, Àr Telefonkatalog-objektets ansvar.
Det finns utrymme för förbÀttringar i strukturen i vÄr anvÀndargrÀnssnittsklass. LÄt oss skapa en metod inlagg(self) som hanterar instruktionen för att lÀgga till ett nytt inlÀgg:
class TelefonkatalogApplikation:
def __init__(self):
self.__katalog = Telefonkatalog()
def hjalp(self):
print("instruktioner: ")
print("0 avsluta")
print("1 tillsÀtt inlÀgg")
# separation of concerns i praktiken: en ny metod för att lÀgga till ett inlÀgg
def inlagg(self):
namn = input("namn: ")
nummer = input("nummer: ")
self.__katalog.tillsatt_nummer(namn, nummer)
def exekvera(self):
self.hjalp()
while True:
print("")
instruktion = input("instruktion: ")
if instruktion == "0":
break
elif instruktion == "1":
self.inlagg()
applikation = TelefonkatalogApplikation()
applikation.exekvera()Separation of concerns-principen strÀcker sig Àven till metodnivÄ. Vi skulle kunna ha hela anvÀndargrÀnssnittets funktionalitet i en enda komplicerad while-loop, men det Àr bÀttre att separera varje funktionalitet i en egen metod. Ansvaret för exekvera()-metoden Àr bara att delegera de instruktioner som anvÀndaren skriver in till relevanta metoder. Detta hjÀlper till att hantera den vÀxande komplexiteten i vÄrt program. Om vi till exempel senare vill Àndra hur det fungerar att lÀgga till inlÀgg, Àr det omedelbart klart att vi dÄ mÄste fokusera vÄra anstrÀngningar pÄ inlagg()-metoden.
LÄt oss inkludera funktionalitet för att söka efter inlÀgg i vÄrt anvÀndargrÀnssnitt. Detta bör ocksÄ ha sin egen metod:
class TelefonkatalogApplikation:
def __init__(self):
self.__katalog = Telefonkatalog()
def hjalp(self):
print("instruktioner: ")
print("0 avsluta")
print("1 tillsÀtt inlÀgg")
print("2 sök")
def inlagg(self):
namn = input("namn: ")
nummer = input("nummer: ")
self.__katalog.tillsatt_nummer(namn, nummer)
def sok(self):
namn = input("namn: ")
numren = self.__katalog.hamta_nummer(namn)
if numren == None:
print("nummer okÀnd")
return
for nummer in numren:
print(nummer)
def exekvera(self):
self.hjalp()
while True:
print("")
instruktion = input("instruktion: ")
if instruktion == "0":
break
elif instruktion == "1":
self.inlagg()
elif instruktion == "2":
self.sok()
else:
self.hjalp()
applikation = TelefonkatalogApplikation()
applikation.exekvera()Vi har nu en enkel fungerande telefonkatalogsapplikation som Àr redo för testning. Följande Àr ett exempel pÄ en exekvering:
instruktioner: 0 avsluta 1 tillsÀtt inlÀgg 2 sök
instruktion: 1 namn: Erik nummer: 02-123456
instruktion: 1 namn: Erik nummer: 045-4356713
instruktion: 2 namn: Erik 02-123456 045-4356713
instruktion: 2 namn: Emilia nummer okÀnd
instruktion: 0
För en sÄ enkel applikation har vi skrivit ganska mycket kod. Om vi hade skrivit allt i den enda while-loopen hade vi förmodligen kunnat komma undan med mycket mindre kod. Det Àr dock ganska lÀtt att lÀsa koden, strukturen Àr tydlig och vi borde inte ha nÄgra problem med att lÀgga till nya funktioner.
Steg 3: Importera data frÄn en fil
LÄt oss anta att vi redan har nÄgra telefonnummer lagrade i en fil, och att vi vill lÀsa detta nÀr programmet startar. Datafilen Àr i följande CSV-format:
Erik;02-1234567;045-4356713
Emilia;040-324344Hantering av filer Àr helt klart ett eget ansvarsomrÄde, sÄ det förtjÀnar en klass för sig:
class FilHanterare():
def __init__(self, fil):
self.__fil = fil
def ladda(self):
namnen = {}
with open(self.__fil) as f:
for rad in f:
delar = rad.strip().split(';')
namn, *numren = delar
namnen[namn] = numren
return namnenKonstruktormetoden tar namnet pÄ filen som sitt argument. Metoden ladda(self) lÀser innehÄllet i filen. Varje rad delas upp i tvÄ delar: ett namn och en lista med siffror. Sedan lÀggs dessa till i en ordbok, med namnet som nyckel och listan som vÀrde.
Metoden anvÀnder en smidig Python-funktion: det Àr möjligt att först vÀlja nÄgra objekt frÄn en lista separat och sedan ta resten av objekten i en ny lista. Du kan se ett exempel pÄ detta nedan. Du kanske minns frÄn modul 6 att strÀngmetoden split returnerar en lista.
lista = [1, 2, 3, 4, 5]
forsta, andra, *resten = lista
print(forsta)
print(andra)
print(resten)1 2 [3, 4, 5]
Tecknet * framför variabelnamnet resten i ovanstÄende exempel betyder att den sista variabeln ska innehÄlla alla ÄterstÄende poster i listan, frÄn den tredje och framÄt.
Vi bör absolut testa filhanteraren separat innan vi inkluderar den i vÄr applikation:
t = FilHanterare("katalog.txt")
print(t.ladda()){'Erik': ['02-1234567', '045-4356713'], 'Emilia': ['040-324344']}
Eftersom filhanteraren verkar fungera bra kan vi lÀgga till den i vÄr applikation. LÄt oss anta att vi vill lÀsa filen som det första varje gÄng programmet körs. Den logiska platsen för att lÀsa filen skulle vara konstruktören för klassen TelefonkatalogApplikation:
class TelefonkatalogApplikation:
def __init__(self):
self.__katalog = Telefonkatalog()
self.__fil = FilHanterare("katalog.txt")
# lÀgg till namn och nummer frÄn filen till telefonkatalogen
for namn, numren in self.__fil.ladda().items():
for nummer in numren:
self.__katalog.tillsatt_nummer(namn, nummer)
# annan kodDenna funktionalitet bör ocksÄ testas. NÀr vi har försÀkrat oss om att filens innehÄll Àr tillgÀngligt via anvÀndargrÀnssnittet i vÄr applikation kan vi gÄ vidare till nÀsta steg.
Steg 4: exportera data till en fil
Den sista funktionen i vÄr grundversion av applikationen Àr att spara innehÄllet i telefonkatalogen tillbaka i samma fil som data lÀstes frÄn.
Detta innebÀr en förÀndring av Telefonkatalog-klassen. Vi mÄste kunna exportera innehÄllet i telefonkatalogen:
class Telefonkatalog:
def __init__(self):
self.__personer = {}
# ...
# returnera alla inlÀgg (i ordlistsformat)
def alla_uppgifter(self):
return self.__personerSjÀlva sparandet till filen bör hanteras av FilHanterare-klassen. LÄt oss lÀgga till metoden spara som tar en ordlistsrepresentation av telefonkatalogen som argument:
class FilHanterare():
def __init__(self, fil):
self.__fil = fil
def ladda(self):
# ...
def spara(self, katalog: dict):
with open(self.__fil, "w") as f:
for namn, numren in katalog.items():
rad = [namn] + numren
f.write(";".join(rad) + "\n")Sparandet bör ske nÀr programmet avslutas. LÄt oss lÀgga till en metod för detta ÀndamÄl i anvÀndargrÀnssnittet och anropa den innan vi bryter ut ur while-loopen:
class TelefonkatalogApplikation:
# resten av koden för anvÀndargrÀnssnittet
# metod som exekveras till programmet avslutas
def avsluta(self):
self.__fil.spara(self.__katalog.alla_uppgifter())
def exekvera(self):
self.hjalp()
while True:
print("")
instruktion = input("instruktion: ")
if instruktion == "0":
self.avsluta()
break
elif instruktion == "1":
self.inlagg()
elif instruktion == "2":
self.sok()
else:
self.hjalp()Objekt i en ordlista
I nÀsta övning ombeds du att Àndra din telefonkatalog sÄ att vÀrdena i ordlistan Àr objekt, inte listor.
Det Àr inget konstigt med det i sig, men det Àr första gÄngen pÄ den hÀr kursen som nÄgot sÄdant föreslÄs, sÄ lÄt oss gÄ igenom ett enklare exempel innan vi dyker ner i övningen.
HÀr har vi en applikation som hÄller reda pÄ hur mÄnga övningar som studenterna har gjort pÄ en kurs. Varje elevs antal övningar lagras i ett enkelt objekt:
class Uppgiftsraknare:
def __init__(self):
self.__uppgifter = 0
def klar(self):
self.__uppgifter += 1
def gjorda(self):
return self.__uppgifterFöljande huvudfunktion anvÀnder klassen ovan:
studeranden = {}
print("markerar uppgifter")
while True:
namn = input("studerande: ")
if len(namn) == 0:
break
# skapa ett nytt objekt ifall det inte finns Ànnu
if not namn in studeranden:
studeranden[namn] = Uppgiftsraknare()
# lÀgg till en ny utförd uppgift i rÀknaren
studeranden[namn].klar()
print()
print("gjorda uppgifter:")
for studerande, uppgifter in studeranden.items():
print(f"{studerande} uppgifter {uppgifter.gjorda()} st")Att exekvera koden ovan kunde se ut enligt följande:
markerar uppgifter studerande: peter studerande: sara studerande: anton studerande: sara studerande: jonas studerande: jonas studerande: anton studerande: sara studerande:
gjorda uppgifter: peter uppgifter 1 st anton uppgifter 2 st sara uppgifter 3 st jonas uppgifter 2 st
Det finns ett par saker att tÀnka pÄ i exemplet ovan. NÀr anvÀndaren matar in ett namn kontrollerar programmet först om namnet redan Àr en nyckel i ordlistan. Om namnet inte finns skapas ett nytt objekt som lÀggs till som en post i ordlistan:
if not namn in studeranden:
studeranden[namn] = Uppgiftsraknare()Efter detta kan vi vara sÀkra pÄ att objektet existerar, kopplat till namnet pÄ den student som anvÀnds som nyckel. Antingen skapades det precis, eller sÄ fanns det redan frÄn en tidigare iteration av loopen. I vilket fall som helst kan vi nu hÀmta objektet med nyckeln och kalla metoden klar:
studeranden[namn].klar()Raden ovan innehÄller egentligen tvÄ separata hÀndelser. Vi kan lika gÀrna anvÀnda en hjÀlpvariabel och skriva den pÄ tvÄ separata kodrader:
studerandes_raknare = studeranden[namn]
studerandes_raknare.klar()OBS: Ăven om objektet hĂ€r tilldelas en hjĂ€lpvariabel, sĂ„ finns objektet kvar i ordlistan precis som tidigare. HjĂ€lparvariabeln innehĂ„ller en referens till objektet i ordlistan.
Om du inte Àr helt sÀker pÄ vad som egentligen hÀnder i koden ovan kan du prova med visualiseringsverktyget.
NÄgra avslutande anmÀrkningar
Strukturen i Telefonkatalog-exemplet ovan följer de grundlÀggande principerna för objektorienterad programmering ganska vÀl. Den centrala principen Àr att identifiera de olika ansvarsomrÄdena i programmet och fördela dessa logiskt mellan de olika klasserna och metoderna. Ett av de viktigaste motiven för denna uppdelning Àr att hantera komplexiteten. Ett annat viktigt motiv Àr att en logisk uppdelning av ansvarsomrÄden - modularitet i facktermer - ofta gör koden lÀttare att underhÄlla och bygga vidare pÄ.
I de programvarupaket som utvecklas och anvÀnds ute i vÀrlden Àr underhÄll och utbyggnad, dvs felsökning av befintlig programvara och implementering av nya funktioner, den absolut dyraste delen av utvecklingen. Korrekt implementerad modularitet Àr ekonomiskt sett en mycket viktig funktion i programvaruutvecklingen.
Det finns nÄgra fler objektorienterade programmeringsprinciper som Àr vÀrda att lyfta fram hÀr. Telefonkatalog Àr ett bra exempel pÄ hur den centrala programlogiken kan (och bör) separeras frÄn bÄde anvÀndargrÀnssnitt och datalagring. Detta Àr viktigt pÄ grund av ett par olika skÀl. För det första gör denna separation det möjligt att testa koden i mindre enheter, en klass och metod i taget. För det andra, eftersom kÀrnlogiken nu Àr oberoende av grÀnssnitten som kommer till omvÀrlden, Àr det möjligt att i viss utstrÀckning Àndra implementeringen av antingen kÀrnlogiken eller grÀnssnitten utan att hela applikationen gÄr sönder.
Filhanteringen i Telefonkatalog-programmet gÄr till pÄ följande sÀtt: Programmet lÀser filen en enda gÄng, nÀr det startar. Efter detta lagras all data i variabler i programmet. NÀr programmet avslutas lagras alla data igen, vilket i praktiken innebÀr att filen skrivs över. I de flesta fall Àr detta det rekommenderade sÀttet att hantera externa filer, eftersom det ofta Àr mycket mer komplicerat att redigera data pÄ plats.
Det finns mÄnga bra guideböcker för att lÀra sig om god programmeringspraxis. En sÄdan Àr Clean Code av Robert Martin. Kodexemplen i boken Àr dock implementerade i Java, sÄ att arbeta igenom exemplen kan vara ganska besvÀrligt vid denna tidpunkt i din programmeringskarriÀr, Àven om boken i sig rekommenderas mycket av kurspersonalen. Teman som lÀtt underhÄllen, utbyggbar kod av god kvalitet kommer att utforskas ytterligare pÄ kurserna Software Development Methods och Software Engineering.
Att skriva kod enligt etablerade principer för objektorienterad programmering har ett pris. Du kommer sannolikt att fÄ skriva mer kod Àn om du skulle skriva din implementation i en enda kontinuerlig stöt av spaghettikod. En av de viktigaste fÀrdigheterna hos en programmerare Àr att bestÀmma det bÀsta tillvÀgagÄngssÀttet för varje situation. Ibland Àr det nödvÀndigt att bara hacka ihop nÄgot snabbt för omedelbar anvÀndning. à andra sidan, om det inom överskÄdlig framtid kan förvÀntas att koden kommer att ÄteranvÀndas, underhÄllas eller vidareutvecklas, antingen av dig eller, mer kritiskt, av nÄgon helt annan, blir programkodens lÀsbarhet och logiska modularitet vÀsentlig. Oftast Àr det sÄ att om det Àr vÀrt att göra, sÄ Àr det vÀrt att göra det bra, Àven i de allra tidigaste utvecklingsstadierna.
För att avsluta denna del av materialet kommer du att implementera ytterligare en större applikation.
Epilog
För att avsluta den hÀr delen av materialet ÄtervÀnder vi till anvÀndargrÀnssnittet i telefonkatalogsexemplet för en stund.
class TelefonkatalogApplikation:
def __init__(self):
self.__katalog = Telefonkatalog()
self.__fil = FilHanterare("katalog.txt")
# annan kod
applikation = TelefonkatalogApplikation()
applikation.exekvera()Ett TelefonkatalogApplikation-objekt innehÄller bÄde ett Telefonkatalog-objekt och ett FilHanterare-objekt. Namnet pÄ den fil som skickas till FilHanterare Àr för nÀrvarande hÄrdkodat i TelefonkatalogApplikation-klassen. Detta Àr en helt irrelevant detalj nÀr det gÀller applikationens anvÀndargrÀnssnitt. Faktum Àr att den bryter mot principen om separation of concerns: var ett Telefonkatalog-objekt sparar sitt innehÄll ska inte vara en angelÀgenhet för en TelefonkatalogApplikation, men om vi vill Àndra platsen mÄste vi Àndra koden för TelefonkatalogApplikation.
Det skulle vara en bÀttre idé att skapa ett FilHanterare-objekt nÄgonstans utanför TelefonkatalogApplikation-klassen och skicka det som ett argument till applikationen:
class TelefonkatalogApplikation:
def __init__(self, fil):
self.__katalog = Telefonkatalog()
self.__fil = fil
# annan kod
# skapar ett objekt för att hantera sparning av filen
forvaringstjanst = FilHanterare("katalog.txt")
# och passerar det som argument till TelefonkatalogApplikation-objektets konstruktor
applikation = TelefonkatalogApplikation(forvaringstjanst)
applikation.exekvera()Detta tar bort ett onödigt beroende frÄn TelefonkatalogApplikation-klassen. Om namnet pÄ filen Àndras behöver anvÀndargrÀnssnittet inte lÀngre Àndras. Vi behöver bara skicka ett annat argument till konstruktören:
class TelefonkatalogApplikation:
def __init__(self, fil):
self.__katalog = Telefonkatalog()
self.__fil = fil
# annan kod
# anvÀnd ett annat filnamn
forvaringstjanst = FilHanterare("ny_katalog.txt")
applikation = TelefonkatalogApplikation(forvaringstjanst)
applikation.exekvera()Denna förÀndring gör det ocksÄ möjligt för oss att övervÀga mer exotiska lagringsplatser, till exempel en molntjÀnst pÄ internet. Vi behöver bara implementera en klass som anvÀnder molntjÀnsten och erbjuder TelefonkatalogApplikation exakt samma metoder som FilHanterare.
En instans av denna nya "moln hanterar"-klass kan skickas som ett argument till konstruktören och inte en enda rad kod behöver Àndras i anvÀndargrÀnssnittet:
class MolnHanterare:
# kod som sparar innehÄllet av telefonkatalogen i en molntjÀnst pÄ internet
forvaringstjanst = MolnHanterare("amazon-cloud", "anvandarnamn", "losenord")
applikation = TelefonkatalogApplikation(forvaringstjanst)
applikation.exekvera()Som du har sett tidigare har den hÀr typen av tekniker ett pris, eftersom det blir mer kod att skriva, sÄ en programmerare mÄste övervÀga om det Àr en acceptabel avvÀgning.
Den teknik som beskrivs ovan kallas beroendeinjektion. Som namnet antyder Àr tanken att alla beroenden som krÀvs av ett objekt ska tillhandahÄllas utanför objektet. Det Àr ett mycket anvÀndbart verktyg i en programmerares verktygslÄda, eftersom det gör det lÀttare att implementera nya funktioner i program och underlÀttar automatisk testning. Detta tema kommer att utforskas ytterligare pÄ de tidigare nÀmnda kurserna Software Development och Software Engineering.
Svara vÀnligen pÄ en snabb enkÀt om denna del av kursen.
Log in to view the quiz
Se dina poÀng genom att klicka pÄ cirkeln nere till höger av sidan.