Инкапсуляция, скрывающая детали реализации
Ещё одним аспектом, обычно связанным с ООП, является понятие инкапсуляции, которое означает, что детали реализации объекта недоступны для кода, использующего этот объект. Поэтому единственным способом взаимодействия с объектом является через его публичный API; код, использующий объект, не должен иметь доступа к внутренним частям объекта и напрямую изменять данные или поведение. Это позволяет программисту изменять и рефакторить внутренние части объекта, не меняя при этом код, использующий объект.
Мы обсуждали, как контролировать инкапсуляцию в главе 7: мы можем использовать ключевое слово pub
, чтобы определить, какие модули, типы, функции и методы в нашем коде должны быть публичными, и по умолчанию все остальное является приватным. Например, мы можем определить структуру AveragedCollection
, которая имеет поле, содержащее вектор значений типа i32
. Структура также может иметь поле, содержащее среднее значение элементов в векторе, что означает, что среднее значение не нужно вычислять при каждом запросе. Другими словами, AveragedCollection
будет кешировать вычисленное среднее значение для нас. В листинге 17-1 приведено определение структуры AveragedCollection
.
Filename: src/lib.rs
pub struct AveragedCollection {
list: Vec<i32>,
average: f64,
}
Листинг 17-1: Структура AveragedCollection
, которая хранит список целых чисел и среднее значение элементов в коллекции
Структура помечена pub
, чтобы другой код мог использовать её, но поля внутри структуры остаются приватными. Это важно в этом случае, потому что мы хотим гарантировать, что при добавлении или удалении значения из списка среднее значение также обновляется. Мы это делаем, реализуя методы add
, remove
и average
на структуре, как показано в листинге 17-2.
Filename: src/lib.rs
impl AveragedCollection {
pub fn add(&mut self, value: i32) {
self.list.push(value);
self.update_average();
}
pub fn remove(&mut self) -> Option<i32> {
let result = self.list.pop();
match result {
Some(value) => {
self.update_average();
Some(value)
}
None => None,
}
}
pub fn average(&self) -> f64 {
self.average
}
fn update_average(&mut self) {
let total: i32 = self.list.iter().sum();
self.average = total as f64 / self.list.len() as f64;
}
}
Листинг 17-2: Реализации публичных методов add
, remove
и average
на AveragedCollection
Публичные методы add
, remove
и average
— единственный способ доступа к или модификации данных в экземпляре AveragedCollection
. Когда элемент добавляется в list
с использованием метода add
или удаляется с использованием метода remove
, реализации каждого вызывают приватный метод update_average
, который также обрабатывает обновление поля average
.
Мы оставляем поля list
и average
приватными, чтобы внешний код не имел возможности напрямую добавлять или удалять элементы из поля list
; в противном случае поле average
может быть устаревшим при изменении list
. Метод average
возвращает значение из поля average
, позволяя внешнему коду читать average
, но не модифицировать его.
Поскольку мы инкапсулировали детали реализации структуры AveragedCollection
, мы можем легко изменить аспекты, такие как структура данных, в будущем. Например, мы могли бы использовать HashSet<i32>
вместо Vec<i32>
для поля list
. Пока сигнатуры публичных методов add
, remove
и average
остаются такими же, код, использующий AveragedCollection
, не потребуется изменять. Если мы сделаем list
публичным, это не обязательно будет так: HashSet<i32>
и Vec<i32>
имеют разные методы для добавления и удаления элементов, поэтому внешний код, вероятно, должен был бы измениться, если он напрямую модифицировал list
.
Если инкапсуляция является обязательным аспектом для того, чтобы язык считался объектно-ориентированным, то Rust соответствует этому требованию. Возможность использовать pub
или нет для разных частей кода позволяет инкапсулировать детали реализации.