9.1. Accessor Encapsulation

9.1.1. Problem

  • Not only Java, but C++ and many others too

  • Setter

  • Getter

  • Deleter

>>> class Point:
...     _x: int
...     _y: int
...     _z: int
...
...     def set_x(self, value):
...         self._x = value
...
...     def get_x(self):
...         return self._x
...
...     def del_x(self):
...         self._x = None
...
...     def set_y(self, value):
...         self._y = value
...
...     def get_y(self):
...         return self._y
...
...     def del_y(self):
...         self._y = None
...
...     def set_z(self, value):
...         self._z = value
...
...     def get_z(self):
...         return self._z
...
...     def del_z(self):
...         self._z = None

End-users code:

>>> pt = Point()
>>>
>>> pt.set_x(1)
>>> pt.set_y(2)
>>> pt.set_z(3)
>>>
>>> position = pt.get_x(), pt.get_y(), pt.get_z()
>>> print(position)
(1, 2, 3)

Let's introduce feature, that z-axis cannot be negative (value below 0). Although we cannot change previously defined API (methods). If we change API it will break our end-users code and we don't want that. However we can change our class code without any problem.

Not a big of a deal. We already have placeholders to inject such validation. What was previously considered as an overhead, eventually gave us future proof. Very good!

9.1.2. Solution

  • But this is the Java way...

>>> class Point:
...     _x: int
...     _y: int
...     _z: int
...
...     def set_x(self, value):
...         self._x = value
...
...     def get_x(self):
...         return self._x
...
...     def del_x(self):
...         self._x = None
...
...     def set_y(self, value):
...         self._y = value
...
...     def get_y(self):
...         return self._y
...
...     def del_y(self):
...         self._y = None
...
...     def set_z(self, value):
...         if value < 0:
...             raise ValueError('Value must be greater than 0')
...         self._z = value
...
...     def get_z(self):
...         return self._z
...
...     def del_z(self):
...         self._z = None

End-users code:

>>> pt = Point()
>>>
>>> pt.set_x(1)
>>> pt.set_y(2)
>>> pt.set_z(3)
>>>
>>> position = pt.get_x(), pt.get_y(), pt.get_z()
>>> print(position)
(1, 2, 3)

And new feature is working:

>>> pt.set_z(-1)
Traceback (most recent call last):
ValueError: Value must be greater than 0

9.1.3. Encapsulation

  • Hiding an object's state

  • Requiring all interaction to be performed through an object's methods

  • Allows for changing the internal implementation of an object without affecting the rest of the code

Defining setter, getter and deleter methods for each property is not a valid way to do an encapsulation. In Java there is project Lombok which can generate setters and getters for your fields automatically and you can overwrite only those which need to be overwritten. But, this not a sustainable solution and it requires a 3rd-party library installation as dependency.

Real encapsulation is about something else. It is about creating an abstraction over your implementation in order to be allowed to change it in future.

Cartesian Coordinates:

>>> class Point:
...     def __init__(self, x, y, z):
...         self.x = x
...         self.y = y
...         self.z = z
...
...     def set_coordinates(self, x, y, z):
...         self.x = x
...         self.y = y
...         self.z = z
...
...     def get_coordinates(self):
...         return self.x, self.y, self.z
...
...     def del_coordinates(self):
...         self.x = None
...         self.y = None
...         self.z = None
>>>
>>>
>>> pt = Point(1, 2, 3)
>>> pt.set_coordinates(4, 5, 6)
>>> pt.get_coordinates()
(4, 5, 6)

Spherical Coordinates:

>>> class Point:
...     def __init__(self, r, theta, phi):
...         self.r = r
...         self.theta = theta
...         self.phi = phi
...
...     def set_coordinates(self, r, theta, phi):
...         self.r = r
...         self.theta = theta
...         self.phi = phi
...
...     def get_coordinates(self):
...         return self.r, self.theta, self.phi
...
...     def del_coordinates(self):
...         self.r = None
...         self.theta = None
...         self.phi = None
>>>
>>>
>>> pt = Point(1, 2, 3)
>>> pt.set_coordinates(4, 5, 6)
>>> pt.get_coordinates()
(4, 5, 6)

Now, I can change from Cartesian coordinate system to spherical coordinates without a big of a hassle and without breaking public API.

../../_images/encapsulation-spherical-1.png

Figure 9.1. Source: [2]

../../_images/encapsulation-spherical-2.png

Figure 9.2. Source: [1]

9.1.4. References