Определение
Значение термина «инкапсуляция» расплывчато и отличается от источника к источнику. Принято считать, что инкапсуляция — один из основополагающих принципов ООП, хотя некоторые научные статьи вовсе упускают инкапсуляцию из списка. К примеру, Джон Митчелл в книге «Концепты в языках программирования» при перечислении основных концептов в ООП упоминает только абстракцию — термин который принято считать близким к инкапсуляции по значению, но все-же более обширным и высокоуровневым. С другой стороны, Роберт Мартин в его книге «Чистая архитектура» явно говорит о том, что инкапсуляция, наследование и полиморфизм считается фундаментом ООП.
Разнообразие определений, данных термину «инкапсуляция», сложно привести к общему знаменателю. В целом можно выделить два подхода к значению этого термина. Инкапсуляция может быть рассмотрена как:
- связь данных с методами которые этими данными управляют;
- набор инструментов для управления доступом к данным или методам которые управляют этими данными.
Инкапсуляция как связь
Подобного рода трактовка термина «инкапсуляция» очень проста в объяснении. В данном случае, любой класс в котором есть хотя бы одна переменная и один метод который ею управляет наглядно демонстрирует этот принцип.
#!/usr/bin/python3
class Phone:
number = "111-11-11"
def print_number(self):
print( "Phone number is: ", self.number )
my_phone = Phone()
my_phone.print_number()
input( "Press Enter to exit" )
Класс “Phone” объединяет данные в переменной “number” с методом “print_number()”
Можно создать класс, который состоит только из методов (и не содержит переменных), что может быть удобно в некоторых языках программирования. Также возможно создать класс содержащий только данные, без методов, чего, во многих случаях, следует избегать. Обе практики следует применять в случае необходимости и их отношение к «объединяющей» инкапсуляции спорно.
Инкапсуляция как управление доступом
Объяснение концепции ограничения доступа к данным или методам требует гораздо большего количества деталей. Прежде всего, в этом контексте термин «доступ» следует понимать как способность видеть и / или изменять внутреннее содержимое класса. Существует несколько уровней доступа, предоставляемых большинством ООП языков. Обобщая можно сказать что данные объекта могут быть:
- публичными (
public
) — данные доступны всем; - приватными (
private
) — данные доступны только объекту/классу которому они принадлежат.
Большинство языков имеют дополнительные степени доступа, которые находятся между этими границами. К примеру, в C++ и Python3 есть три уровня доступа: публичный, защищенный и приватный; C# добавляет ключевое слово «внутренний» (internal
) в список.
Стоит отметить, что в большинстве языков программирования, уровень доступа к любым данным установлен по умолчанию. Например, в C++ по умолчанию уровень доступа к данным в классе задан как приватный— к его данным могут обращаться только члены и друзья класса. Стандартный уровень доступа к структуре (struct
) в C++ отличается — он публичный, и данные в такой структуре могут быть доступны любому. Уровень доступа для переменных и методов класса в Python 3 полностью зависит от синтаксиса.
Примеры
Инкапсуляция
Python 3 предоставляет 3 уровня доступа к данным:
- публичный (
public
, нет особого синтаксиса,publicBanana
); - защищенный (
protected
, одно нижнее подчеркивание в начале названия,_protectedBanana
); - приватный (
private
, два нижних подчеркивания в начала названия,__privateBanana
).
Для краткости и простоты, только два базовых уровня (приватный и публичный) освещены в примере.
#!/usr/bin/python3
class Phone:
username = "Kate" # public variable
__how_many_times_turned_on = 0 # private variable
def call(self): # public method
print( "Ring-ring!" )
def __turn_on(self): # private method
self.__how_many_times_turned_on += 1
print( "Times was turned on: ", self.__how_many_times_turned_on )
my_phone = Phone()
my_phone.call()
print( "The username is ", my_phone.username )
# my_phone.turn_on()
# my_phone.__turn_on()
# print( “Turned on: “, my_phone.__how_many_times_turned_on)
# print( “Turned on: “, my_phone.how_many_times_turned_on)
# will produce an error
input( "Press Enter to exit" )
Доступ к публичным переменным и методам можно получить из основной программы. Попытка получить приватные данные или запустить приватный метод приведет к ошибке.
Нарушение инкапсуляции
Сам язык предоставляет программисту синтаксический инструмент, который может обойти инкапсуляцию. Читать и изменять частные переменные и вызывать частные функции все же возможно.
#!/usr/bin/python3
class Phone:
username = "Kate" # public variable
__serial_number = "11.22.33" # private variable
__how_many_times_turned_on = 0 # private variable
def call(self): # public method
print( "Ring-ring!" )
def __turn_on(self): # private method
self.__how_many_times_turned_on += 1
print( "Times was turned on: ", self.__how_many_times_turned_on )
my_phone = Phone()
my_phone._Phone__turn_on()
my_phone._Phone__serial_number = "44.55.66"
print( "New serial number is ", my_phone._Phone__serial_number )
input( "Press Enter to exit" )
Несколько слов о Магии
Существуют методы, так называемые «магические методы» («magic methods») или «специальные методы» («special methods»), которые позволяют классам определять свое поведение в отношении стандартных языковых операторов. Примером таких языковых операторов могут служить следующие выражения:
x > y
x[ i ]
Python 3 поддерживает множество таких методов, полный список можно найти на странице официальной документации языка. __init__
(инициализатор) является наиболее часто используемым из них и запускается при создании нового объекта класса. Другой, __lt__
(расширенное сравнение), определяет правила для сравнения двух объектов пользовательского класса. Такие методы не попадают в категорию «приватных» или «публичных», поскольку служат другим целям и корнями глубоко уходят во внутреннюю структуру языка.
#!/usr/bin/python3
class Phone:
def __init__(self, number): # magic method / inititalizer
print( "The Phone object was created" )
self.number = number
def __lt__(self, other): # magic method / rich comparison
return self.number < other.number
my_phone = Phone(20)
other_phone = Phone(30)
if my_phone < other_phone:
print( "Two instances of custom class were compared" )
print( "'__lt__' was called implicitly" )
if my_phone.__lt__(other_phone):
print( "Now, '__lt__' was used explicitly" )
input( "Press Enter to exit" )
Магические методы могут быть вызваны любым пользователем таким же образом как и любой публичный метод в Питоне, однако они предназначены для неявного использования в своих особых случаях. Специальный случай для метода __init__
— инициализация нового объекта класса. __lt__
служит для сравнения двух объектов.
Заключение
Python3 не обеспечивает ограниченный доступ к каким-либо переменным или методам класса. Данные, которые должны быть скрыты, на самом деле могут быть прочитаны и изменены. В Python3 инкапсуляция является скорее условностью, и программист должен самостоятельно заботиться о ее сохранении.
Источники
- John C. Mitchell, Concepts in programming languages
- Robert C. Martin, Clean Architecture, A Craftsman’s Guide to Software Structure and Design