Centralt innehåll

1. Inkapsling

En klass samlar egenskaper och beteenden, dvs attribut och metoder. Det hjälper programmeraren att strukturera koden. Tanken är att den som använder klassen inte ska begå misstag som att oavsiktligt ändra klassens attribut på ett felaktigt sätt. Det kallas för inkapsling.

Den allmänna idén med inkapsling är att egenskaperna (attributen) för ett objekt är interna, dvs något som bara rör objektet. När man säger att data är interna säger man att de bör skyddas från andra okontrollerad yttre ändringar. Idén med inkapsling är alltså att skydda attributen från okontrollerade ändringar.

1.1 Varför behövs inkapsling?

Med olika exempel förklaras här varför objekten själv bör ha kontroll över sina attribut.

class Square:
    def __init__(self, side):
        # Both sides gets the same size
        self.height = side
        self.width = side

    def __str__(self):
        return f'The square has the width {self.width} and the height is the same {self.height}'    

square = Square(2)
print(square)
square.height = 3
square.width = 3
print(square)
ger utskriften
The square has the width 2 and the height is the same 2
The square has the width 3 and the height is the same 3
Attributen width och height har samma värde så länge som programmeraren ändrar båda attributen till samma värde.

Det skulle gå att ändra t.ex. width utan att ändra height och då får man en kvadrat med olika längd på sidorna.

class Square:
    def __init__(self, side):
        # Both sides gets the same size
        self.height = side
        self.width = side

    def __str__(self):
        return f'The square has the width {self.width} and the height is the same {self.height}'    

square = Square(2)
print(square)
square.width = 3
print(square)
ger utskriften
The square has the width 2 and the height is the same 2
The square has the width 3 and the height is the same 2
Här får kvadraten olika värden på width och height och är egentligen ingen kvadrat.

Om man lägger till metoderna set_width och set_height för att sätta värdena går det att kontrollera att width och heigth har samma värde hela tiden.

class Square:
    def __init__(self, side):
        # Both sides gets the same size
        self.height = side
        self.width = side

    def __str__(self):
        return f'The square has the width {self.width} and the height is the same {self.height}'    
	
    def set_height(self, new_height):
        # Set a new size to both sides
        self.height = new_height
        self.width = new_height

    def set_width(self, new_width):
        # Set a new size to both sides
        self.width = new_width
        self.height = new_width


square = Square(2)
print(square)
square.set_width(4)
print(square)
ger utskriften
The square has the width 2 and the height is the same 2
The square has the width 4 and the height is the same 4
Det går att kontrollera att kvadratens sidor är lika långa så länge som programmeraren använde metoderna set_height eller set_width för att ändra värdet på sidorna.

Men det går att ändra t.ex. width direkt utan att ändra height om attributet ändras direkt utan att använda metoderna set_height och set_width.

class Square:
    def __init__(self, side):
        # Both sides gets the same size
        self.height = side
        self.width = side

    def __str__(self):
        return f'The square has the width {self.width} and the height is the same {self.height}'    
	
    def set_height(self, new_height):
        # Set a new size to both sides
        self.height = new_height
        self.width = new_height

    def set_width(self, new_width):
        # Set a new size to both sides
        self.width = new_width
        self.height = new_width


square = Square(2)
print(square)
square.width = 4
print(square)
ger utskriften
The square has the width 2 and the height is the same 2
The square has the width 4 and the height is the same 2
Nu är det ingen kvadrat längre, eftersom sidorna är olika långa. Att det går att komma åt attributen direkt och sätta värden gör att det går att gå runt de kontroller som finns i metoderna. På det sättet kan attributen ändras på ett okontrollerat sätt som kan orsaka logiska fel i programmen. Klassen Square är kodad så att man måste anropa metoden set_height eller set_width för att vara säker på att den fungerar korrekt.

Att låta objektet ta hand om sina attribut är alltid bättre. Därför bör man alltid använda objekts metoder hellre än att ändra attributen direkt.

Ett objekt fungerar bör fungera som en slags "black box", där alla attribut göms (information hiding). Man har ett antal metoder som går att anropa, men man behöver inte veta alla detaljer om attribut och hur metoderna kodas inuti klassen. Man behöver inte känna till det interna, bara vad som kan göras med objektet (metoderna).

Tänk på hur det fungerar utanför kod, t.ex. med en bil. När du kör bil trycker du på pedaler för att kontrollera kopplingen, gasa och bromsa. Du köra bilen utan att tänka på vad som exakt händer inne i motorn under tiden. Dvs hur bilen faktiskt utför kopplingen, gasa och bromsa. Det är samma sak med kod. I de flesta fall behöver du inte veta hur ett objekt gör något, så länge det finns en metod du kan anropa som gör det du vill.

2. Åtkomstnivåer

Det bästa är att använda metoder för att ändra attributens värden, men går det att hindra programmeraren från att komma åt attributen direkt?

Åtkomstnivåer används för att skydda din klasser och objekt från oönskad ändring av attributen. Man kan dölja attributen (och metoder) från utsidan, från andra objekt, genom att markera dem med vissa nyckelord.

I Python döljs data genom att ett prefix läggs till attribut. Attribut utan prefix kallas för publika, dvs de kan läsas och ändras hur som helst.
En inledande understreck _ är en rekommendation till programmerar att dessa attribut förmodligen inte bör användas utanför objektet.

class Square:
    def __init__(self, side):
        # Both sides gets the same size
        self._height = side
        self._width = side

    def __str__(self):
        return f'The square has the width {self._width} and the height is the same {self._height}'    

    def set_height(self, new_height):
        # Set a new size to both sides
        self._height = new_height
        self._width = new_height

    def set_width(self, new_width):
        # Set a new size to both sides
        self._width = new_width
        self._height = new_width

square = Square(2)
print(square)
square._width = 3  # Attributes with the prefix _ are recommended NOT to be used outside the object itself.
print(square)
ger utskriften
The square has the width 2 and the height is the same 2
The square has the width 3 and the height is the same 2
Ett attribut med ett inledande understreck _ kallas för skyddande och kan ses som en rekommendation att inte ändra attributet utanför objektet. Men det är fortfarande tillåtet att attributet ändras.

Det går att skydda attributen mer genom att ha två inledande understreck __. Sådana attribut kallas privata.

class Square:
    def __init__(self, side):
        # Both sides gets the same size
        self.__height = side
        self.__width = side

    def __str__(self):
        return f'The square has the width {self.__width} and the height is the same {self.__height}'    
	
    def set_height(self, new_height):
        # Set a new size to both sides
        self.__height = new_height
        self.__width = new_height

    def set_width(self, new_width):
        # Set a new size to both sides
        self.__width = new_width
        self.__height = new_width


square = Square(2)
print(square)
square.__width = 3  # Causes an error or is ignored
print(square)

ger utskriften
The square has the width 2 and the height is the same 2
The square has the width 2 and the height is the same 2
Försöket att sätta square.__width = 3 ignoreras, dvs får ingen effekt.

Betyder det att om alla attribut privata att de inte kan användas utanför objektet?
Tja, inte helt. Det går att komma åt attributen, men det är lite krångligare, och inget en programmerare "råkar" göra av misstag. Vi går inte genom hur det görs här.

Det man försöker göra är att kapsla in attributen, dvs inkapsling, så att det bara är objektet själv som kommer åt att ändra attributens värden.

Många andra objektorienterade språk som implementerar inkapsling av attribut löser problemet på andra sätt. De har strikta regler regler som inte går att komma runt. Python är unikt i det att inkapslingen av attribut är mer som rekommendationer. Rekommendationer, men ändå med de olika åtkomstnivåerna publika, skyddade och privata.

3. Get- och set-metoder

Vi har redan sagt att attribut i allmänhet inte bör röras från utsidan. De är objektets angelägenhet. Om värdena ska ändras kan set-metoder användas, som t.ex. set_width och set_heigth.

Om man behöver läsa värden för attributen kan get-metoder användas.

class Square:
    def __init__(self, side):
        # Both sides gets the same size
        self.__height = side
        self.__width = side

    def __str__(self):
        return f'The square has the width {self.__width} and the height is the same {self.__height}'    
	
    def get_height(self):
        return self.__height

    def set_height(self, new_height):
        # Set a new size to both sides
        self.__height = new_height
        self.__width = new_height

    def get_width(self):
        return self.__width

    def set_width(self, new_width):
        # Set a new size to both sides
        self.__width = new_width
        self.__height = new_width


square = Square(2)
width = square.get_width()
print(width)
square.set_height(3)
width = square.get_width()
print(width)
ger utskriften
2
3
Get-metoder gör att kod utanför objektet kan läsa attributens värden. Set-metoder gör att kod utanför objektet kan ändra attributens värden.

Idén är att en set-metod ska fungera som skydd så att felaktiga värden inte kan anges.

class Square:
    def __init__(self, side):	
        # Both sides gets the same size
        self.__height = side
        self.__width = side

    def __str__(self):
        return f'The square has the width {self.__width} and the height is the same {self.__height}'    
	
    def get_height(self):
        return self.__height

    def set_height(self, new_height):
        if new_height > 0:
            # Set a new size to both sides
            self.__height = new_height
            self.__width = new_height
         else:
             raise Exception("A Squares height needs to be larger than 0")     

    def get_width(self):
        return self.__width

    def set_width(self, new_width):
        if new_width > 0:
            # Set a new size to both sides
            self.__height = new_width
            self.__width = new_width
         else:
             raise Exception("A Squares width needs to be larger than 0")     


square = Square(2)
print(square)
square.set_width(3)
print(square)
square.set_height(-2)
print(square)
ger utskriften
The square has the width 2 and the height is the same 2
The square has the width 3 and the height is the same 3
Traceback (most recent call last):
    square.set_height(-2)
    raise Exception("A Squares height needs to be larger than 0")
Exception: A Squares height needs to be larger than 0
Metoderna set_width och set_height skyddar objektets attribut width och height från att få ett värde som är noll eller mindre.

4. Dekoratörer

När en klass skrivs med inkapsling kan det bli många get- och set-metoder som då minskar läsbarheten. Det finns ett bättre och mer läsbart sätt att göra samma sak utan att använda get- och set-metoder. Dekoratörer (eng. decorators) är ett mer Pythoniskt sätt att använda inkapsling utan get- och set-metoder.

Dekoratören @property används för att hämta ett attribut (motsvarar get-metod), se exemplen i koden för metoderna height och width. Sedan används metodernas namn för att ha en dekoratörer för att ändra värden, i exemplet är det @height.setter och @width.setter

class Square:
    def __init__(self, side):	
        # Both sides gets the same size
        self.__height = side
        self.__width = side

    def __str__(self):
        return f'The square has the width {self.__width} and the height is the same {self.__height}'    

    @property
    def height(self):
      return self.__height

    @height.setter
    def height(self, new_height):
        if new_height > 0:
            # Set a new size to both sides
            self.__height = new_height
            self.__width = new_height
        else:
            raise Exception("A Squares height needs to be larger than 0")     

    @property
    def width(self):
        return self.__width

    @width.setter
    def width(self, new_width):
        if new_width > 0:
            # Set a new size to both sides
            self.__height = new_width
            self.__width = new_width
        else:
            raise Exception("A Squares width needs to be larger than 0")     


square = Square(2)
print(square)
square.width = 3
print(square)
square.height = 4
print(square.height)
ger utskriften
The square has the width 2 and the height is the same 2
The square has the width 3 and the height is the same 3
4
Koden utanför objektet ser ut som den använder attributet direkt, t.ex. square.width = 3, men det är egentligen en metod som anropas, nämligen def width(self, new_width)

4.1 Fördelar med dekoratörer

Begrepp

Klass: En mall för att skapa objekt.

Objekt: En verklig förekomst av en klass.

Attribut: Variabler i en klass.

Metod: En funktion kopplad till ett objekt.

Inkapsling: Att skydda attribut i ett objekt från okontrollerade ändringar.

Åtkomstnivåer: Attribut i objekt kan skyddas på olika sätt. De kan vara publika, skyddade eller privata.

Publika attribut: Attribut som kan läsas och ändras hur som helst.

Skyddade attribut: Attribut med ett understreck som prefix och rekommenderas att inte ändras utanför objektet.

Privata attribut: Attribut med två understreck som prefix och rekommenderas att inte ändras utanför objektet.

Get- och set-metoder: Används för att läsa eller ändra attributens värden.

Dekoratörer: Ett mer läsbart sätt att använda inkapsling utan get- och set-metoder.

Fakta

Övningar

Inlämningsuppgift

login     logout    

Exit tickets