Inkapsling
I objektorienterad programmering avser termen klient ett program som använder en klass eller instanser av en klass. En klass erbjuder klienten tjänster genom vilka klienten kan komma åt de objekt som skapats baserat på klassen. Målen här är att
- användningen av en klass och/eller objekt är så enkel som möjligt ur klientens synvinkel
- integriteten för varje objekt bevaras hela tiden
Ett objekts integritet innebär att objektets tillstånd alltid förblir acceptabelt. I praktiken innebär detta att värdena på objektets attribut alltid är acceptabla. Ett objekt som representerar ett datum ska till exempel aldrig ha 13 som värde för månaden, ett objekt som representerar en student ska aldrig ha ett negativt tal som värde för uppnådda studiepoäng och så vidare.
Låt oss ta en titt på en klass som heter Studerande:
class Studerande:
def __init__(self, namn: str, studerandenummer: str):
self.namn = namn
self.studerandenummer = studerandenummer
self.studiepoang = 0
def tillagg_poang(self, studiepoang):
if studiepoang > 0:
self.studiepoang += studiepoang
Studerande
objektet erbjuder sina klienter metoden tillagg_poang
, som gör det möjligt för klienten att lägga till ett angivet antal studiepoäng till studentens totala antal. Metoden säkerställer att värdet som skickas som argument är över noll. Följande kod lägger till studiepoäng vid tre tillfällen:
oskar = Studerande("Oskar Studerande", "12345")
oskar.tillagg_poang(5)
oskar.tillagg_poang(5)
oskar.tillagg_poang(10)
print("Studiepoäng:", oskar.studiepoang)
Studiepoäng: 20
Trots metoddefinitionen är det fortfarande möjligt att komma åt attributet studiepoang
direkt. Detta kunde resultera i ett felaktigt tillstånd där objektets integritet går förlorad:
oskar = Studerande("Oskar Studerande", "12345")
oskar.studiepoang = -100
print("Studiepoäng:", oskar.studiepoang)
Studiepoäng: -100
Inkapsling
Ett vanligt inslag i objektorienterade programmeringsspråk är att klasserna kan dölja sina attribut för eventuella kunder. Dolda attribut kallas vanligtvis privata. I Python uppnås denna sekretess genom att lägga till två understreck __
i början av attributnamnet:
class Bankkort:
# Attributet nummer är gömt, attributet namn är åtkombart
def __init__(self, nummer: str, namn: str):
self.__nummer = nummer
self.namn = namn
Ett privat attribut är inte direkt synligt för klienten. Försök att referera till det orsakar ett fel. I exemplet ovan är attributet namn lätt att komma åt och ändra:
kort = Bankkort("123456","Robert Rik")
print(kort.namn)
kort.namn = "Peter Pank"
print(kort.namn)
Robert Rik Peter Pank
Ifall man provar få en utskrift av kortnumret så orsakar det däremot ett fel:
kort = Bankkort("123456","Robert Rik")
print(kort.__nummer)
AttributeError: 'Bankkort' object has no attribute '__nummer'
Att dölja attribut från klienter kallas inkapsling. Som namnet antyder är attributet "slutet inne i en kapsel". Klienten erbjuds sedan ett lämpligt gränssnitt (engelska: interface) för att komma åt och bearbeta den data som finns lagrad i objektet.
Låt oss lägga till ett annat inkapslat attribut: saldot på kreditkortet. Den här gången lägger vi också till offentligt synliga metoder som gör det möjligt för klienten att komma åt och ändra saldot:
class Bankkort:
def __init__(self, nummer: str, namn: str, saldo: float):
self.__nummer = nummer
self.namn = namn
self.__saldo = saldo
def tillsatt_pengar(self, mangd: float):
if mangd > 0:
self.__saldo += mangd
def anvand_pengar(self, mangd: float):
if mangd > 0 and mangd <= self.__saldo:
self.__saldo -= mangd
def hamta_saldo(self):
return self.__saldo
kort = Bankkort("123456", "Robert Rik", 5000)
print(kort.hamta_saldo())
kort.tillsatt_pengar(100)
print(kort.hamta_saldo())
kort.anvand_pengar(500)
print(kort.hamta_saldo())
# Detta lyckas inte, eftersom saldot inte är tillräckligt
kort.anvand_pengar(10000)
print(kort.hamta_saldo())
5000 5100 4600 4600
Saldot kan inte ändras direkt eftersom attributet är privat, men vi har inkluderat metoderna tillsatt_pengar
och ta_ut_pengar
för att ändra värdet. Metoden returnera_saldo
returnerar det värde som lagrats i saldo. Metoderna innehåller några rudimentära kontroller för att bibehålla objektets integritet: till exempel kan kortet inte överdras.
En kort notis om privata attribut, Python och objektorienterad programmering
Det finns sätt att kringgå understryknings __
-notationen för att dölja attribut, som du kan stöta på om du söker efter material online. Inget Python-attribut är verkligen privat, och det är avsiktligt från skaparna av Pythons. Å andra sidan förväntas en Python-programmerare i allmänhet respektera de riktlinjer för synlighet som anges i klasser, och det krävs en särskild ansträngning för att komma runt dessa. I andra objektorienterade programmeringsspråk, till exempel Java, är privata variabler ofta verkligen dolda, och det är bäst om du tänker på privata Python-variabler som sådana också.
Getter och sättare
I objektorienterad programmering kallas metoder som är avsedda för att komma åt och ändra attribut vanligtvis för getter och sättare (eng: setters). Inte alla Python-programmerare använder termerna "getter" och "sättare", men konceptet med egenskaper som beskrivs nedan är mycket liknande, vilket är varför vi kommer att använda den allmänt accepterade objektorienterade programmeringsterminologin här.
Ovan skapade vi några offentliga metoder för att komma åt privata attribut, men det finns ett enklare, "pythoniskt" sätt att komma åt attribut. Låt oss ta en titt på en enkel klass som heter Planbok
med ett enda privat attribut pengar
:
class Planbok:
def __init__(self):
self.__pengar = 0
Vi kan tillägga getter och sättar metoder för att komma åt det privata attributet genom att använda @property
dekoratorn:
class Planbok:
def __init__(self):
self.__pengar = 0
# Gettermetod
@property
def pengar(self):
return self.__pengar
# Sättarmetod
@pengar.setter
def pengar(self, pengar):
if pengar >= 0:
self.__pengar = pengar
Först definierar vi en getter-metod som returnerar den summa pengar som för närvarande finns i plånboken. Sedan definierar vi en sättar-metod som sätter ett nytt värde för pengar-attributet och samtidigt ser till att det nya värdet inte är negativt.
De nya metoderna kan användas på följande sätt:
planbok = Planbok()
print(planbok.pengar)
planbok.pengar = 50
print(planbok.pengar)
planbok.pengar = -30
print(planbok.pengar)
0 50 50
För klienten är det ingen skillnad att använda dessa nya metoder jämfört med att direkt komma åt ett attribut. Parenteser är inte nödvändiga, utan det är helt acceptabelt att ange planbok.pengar = 50
, som om vi helt enkelt tilldelar ett värde till en variabel. Syftet var faktiskt att dölja (dvs. kapsla in) den interna implementeringen av attributet och samtidigt erbjuda ett enkelt sätt att komma åt och ändra den data som lagras i objektet.
I det föregående exemplet finns dock ett litet problem: klienten meddelas inte om att det inte går att ange ett negativt värde för attributet pengar. När ett värde som anges är uppenbart felaktigt är det vanligtvis en bra idé att skapa ett undantag och på så sätt informera klienten. I det här fallet bör undantaget förmodligen vara av typen ValueError
för att visa att det angivna värdet var oacceptabelt.
Här har vi en förbättrad version av klassen, tillsammans med lite kod för att testa den:
class Planbok:
def __init__(self):
self.__pengar = 0
# Gettermetod
@property
def pengar(self):
return self.__pengar
# Sättarmetod
@pengar.setter
def pengar(self, pengar):
if pengar >= 0:
self.__pengar = pengar
else:
raise ValueError("Mängden får inte vara under 0")
planbok.pengar = -30
print(planbok.pengar)
ValueError: Mängden får inte vara under 0
OBS: getter-metoden, dvs @property-dekoratorn
, måste introduceras före sättar-metoden i koden, annars blir det fel när klassen exekveras. Detta beror på att @property-dekoratorn
definierar namnet på det "attribut" som erbjuds till klienten. Sättar-metoden, som läggs till med .setter
, lägger helt enkelt till en ny funktionalitet till den.
Följande exempel har en klass med två privata attribut, tillsammans med getter och sättare för båda. Prova programmet med olika värden som skickas som argument:
class Spelare:
def __init__(self, namn: str, spelnummer: int):
self.__namn = namn
self.__spelnummer = spelnummer
@property
def namn(self):
return self.__namn
@namn.setter
def namn(self, namn: str):
if namn != "":
self.__namn = namn
else:
raise ValueError("Namnet kan inte vara tomt")
@property
def spelnummer(self):
return self.__spelnummer
@spelnummer.setter
def spelnummer(self, spelnummer: int):
if spelnummer > 0:
self.__spelnummer = spelnummer
else:
raise ValueError("Spelnumret måste vara ett positivt heltal")
spelare = Spelare("Fredrik Fotare", 10)
print(spelare.namn)
print(spelare.spelnummer)
spelare.namn = "Fia Futis"
spelare.spelnummer = 11
print(spelare.namn)
print(spelare.spelnummer)
Fredrik Fotare 10 Fia Futis 11
Som avslutning på detta avsnitt ska vi titta på en klass som modellerar en enkel dagbok. Alla attribut är privata, men de hanteras genom olika gränssnitt: dagbokens ägare har getter- och sättar-metoder, men dagboksposterna behandlas med "traditionella" metoder. I det här fallet är det vettigt att neka klienten all tillgång till dagbokens interna datastruktur. Endast de offentliga metoderna är direkt synliga för klienten.
Inkapsling säkerställer också att den interna implementeringen av klassen kan ändras när som helst, förutsatt att det offentliga gränssnittet förblir intakt. Klienten behöver inte veta eller bry sig om huruvida den interna datastrukturen är baserad på listor, ordlistor eller något helt annat.
class Dagbok:
def __init__(self, agare: str):
self.__agare = agare
self.__inlagg = []
@property
def agare(self):
return self.__agare
@agare.setter
def agare(self, agare):
if agare != "":
self.__agare = agare
else:
raise ValueError("Ägaren kan inte vara tom")
def tillsatt_inlagg(self, inlagg: str):
self.__inlagg.append(inlagg)
def skriv_ut(self):
print("Totalt", len(self.__inlagg), "inlägg")
for inlagg in self.__inlagg:
print("- " + inlagg)
dagbok = Dagbok("Peter")
dagbok.tillsatt_inlagg("Idag åt jag gröt")
dagbok.tillsatt_inlagg("Idag lärde jag mig objekt-orienterad programmering")
dagbok.tillsatt_inlagg("Idag lade jag mig tidigt")
dagbok.skriv_ut()
Totalt 3 inlägg
- Idag åt jag gröt
- Idag lärde jag mig objekt-orienterad programmering
- Idag lade jag mig tidigt
Se dina poäng genom att klicka på cirkeln nere till höger av sidan.