Akcesory i mutatory w Pythonie cz.1

Koncepcja programowania obiektowego wiąże się z pojęciem enkapsulacji, czyli ukrywania pól klasy i udostępniania tylko publicznych metod (publicznego API) do odczytu lub zmiany wartości tych pól. W javie np. rozwiązane jest to na poziomie języka za pomocą modyfikatora dostępu – private.

Python nie umożliwia zastosowania takich sposobów hermetyzacji. Stosowanie zmiennych prywatnych w stylu: _zmienna jest tylko kwestią konwencji.

Rozważmy następujący przykład, w którym tworzymy klasę reprezentującą pracownika:

class Employee:
	def __init__(self, name, salary, phone):
	    self.name = name
	    self.salary = salary
	    self.phone = phone

W Javie powinniśmy zadeklarować zmienne name, salary i phone jako prywatne i odwoływać się do nich przez metody dostępowe getters /setters. W Pythonie nie zaleca się pisania metod dostępowych, których jedynym zadaniem jest zwracanie wartości atrybutu lub ustawianie wartości atrybutu. Zamiast tego możemy odnosić się do tych pól bezpośrednio. Ma to wiele zalet, ale traci się korzyści z hermetyzacji np.

johnDoe = Employee('John Doe', 8000, '555 111 22 33')
johnDoe.salary = 9000
print(johnDoe.salary)

>>część druga<<

Akcesory i mutatory w Pythonie cz.2

Stosowanie bezpośredniego dostępu do atrybutów klasy jest problemem w przypadku konieczności dokonywania zmian np. wprowadzenie kontroli poprawności wpisanych danych.

Bazując na kodzie wcześniej utworzonej klasy Employee załóżmy, że chcielibyśmy dokonać walidacji wpisanego numeru telefonu tzn. czy ma odpowiednią ilość cyfr oraz przy okazji usunąć wprowadzone zbędne znaki, które nie są cyframi.

Aby to osiągnąć nie musimy zmieniać sposobu, w jaki komunikujemy się ze zmienną phone. Możemy nadal odwoływać się bezpośrednio poprzez nazwę zmiennej. Jest to możliwe dzięki użyciu dekoratora @property

class Employee:

    def __init__(self, name, salary, phone):
        self.name = name
        self.salary = salary
        self.phone = phone
    @property
        def phone(self):
           return self._phone
    @phone.setter
        def phone(self, value):
            phone = ''.join(filter(lambda x: x.isdigit(), value))
            if len(phone) != 10:
                raise ValueError('Wrong phone number!')
            self._phone = phone

Wywołanie konstruktora klasy ustawia poszczególne atrybuty klasy, przy czym przypisanie argumentu phone do atrybutu self.phone powoduje wywołanie mutatora i dzięki temu walidację wprowadzonego numeru telefonu już w momencie tworzenia obiektu klasy Employee.

Podobnie przypisanie nowej wartości do atrybutu phone wywoła odpowiednią metodę walidującą tzn.

johnDoe = Employee('John Doe', 8000, "555 111 22 33")
johnDoe.phone = '000' #incorrect number length