Инкапсуляция - это сокрытие внутреннего устройства и логики работы класса от кода снаружи. "Сокрытие" здесь значит, что код снаружи класса не может напрямую обращаться к полям объекта, а может делать что-то с объектом только через вызов его публичных методов.
Сокрытие реализуется за счет того, что мы ставим всем полям и методам модификаторы доступа protected
или private
, и только для тех методов, которые должны быть доступны снаружи, ставим public
. Что такое модификаторы доступа, описано в мануале PHP.
"Сокрытие" никак не скрывает код от разработчика, но оно позволяет задать правила работы с классом. Инкапсуляция дает такие плюсы:
- разработчик в методах контролирует данные, передаваемые снаружи, и не позволяет записать недопустимые значения в поля объекта. Например, он может не позволить задать отрицательное количество товара на складе в объекте товара. Таким образом, разработчик может гарантировать корректную работу класса независимо от правильности остального кода.
- разработчик класса определяет, что можно делать с объектом, а что нельзя.
- если мы хотим узнать, что записывается в то или иное поле, нам достаточно изучить код одного класса, и не надо изучать весь код приложения.
- мы можем поменять внутреннюю логику работы класса, не меняя публичные методы, и весь остальной код, который их использует. А если бы код снаружи напрямую работал с полями, то нам пришлось бы искать и исправлять все такие места.
- упрощается понимание кода: чтобы понять, как использовать класс, не надо читать и разбирать весь его код, достаточно прочитать названия публичных методов (и, может быть, комментарии к ним).
Инкапсуляция помогает реализовать принцип единой ответственности (single responsibility), о том, что каждый класс занимается своей задачей, у каждого класса есть своя зона ответственности, и никто другой не должен в нее лезть.
Если проводить аналогии, то можно представить кофе-машину. Ты нажимаешь кнопку (вызываешь публичный метод) и получаешь кофе (результат вызова этого метода), при этом ты не видишь, что происходит внутри нее и тебе не надо в этом разбираться.
Вот пример кода класса с использованием инкапсуляции:
/**
* Объект представляет собой ломаную линию из нескольких сегментов.
* Показаны только публичные методы, остальное скрыто.
*/
class PolyLine
{
// Массив со списком точек
private $points = [];
/**
* Создает новую линию, состоящую из одной начальной точки.
*/
public function __construct(float $x, float $y)
{
$this->points[] = [$x, $y];
}
/**
* Добавляет еще одну точку к ломаной.
*/
public function addPoint(float $x, float $y): void
{
$this->points[] = [$x, $y];
}
/**
* Считает общую длину линии.
*/
public function calculateLength(): float
{
// Оставим написание этого метода как упражнение читателю
}
}
В нем всего 3 публичных метода, включая конструктор, и мы видим, что с объектом можно сделать только три действия:
- создать ломаную, указав начальную точку
- добавить к ломаной еще одну точку
- посчитать длину ломаной как сумму длин отдельных сегментов, длина одного сегмента находится по формуле длины отрезка)
При этом код может проверять передаваемые значения, например, не разрешать 2 раза добавлять одну и ту же точку. Также он не позволяет передать в качестве координат что-либо, кроме чисел. Нам даже не требуется изучать код полностью, чтобы понять, как с ним работать - достаточно глянуть на заголовки публичных методов. Вот пример его использования:
$line = new PolyLine(1, 1);
$line->addPoint(2, 2);
$line->addPoint(4, 7);
echo $line->calculateLength();
Благодаря инкапсуляции мы можем поменять внутреннюю логику класса, например, хранить в массиве $points не массивы из 2 координат, а объекты Point, при этом менять код, работающий с классом, не потребуется.
Замечу, что с помощью расширения Reflection можно обойти инкапсуляцию. Например, библиотека ORM Doctrine использует его, чтобы записывать значения из базы данных в приватные поля объекта. Reflection стоит использовать вдумчиво и в исключительных случаях.
Инкапсуляция возможна не только в объектно-ориентированном программировании. Например, модули в языках вроде Javascript или Python позволяют скрывать внутреннее устройство и делать доступными только отдельные функции и таким образом тоже реализуют принцип инкапсуляции.