Что означает объектно-ориентированное?

В сообществе разработчиков отсутствует консенсус относительно особенностей языка, который он должен иметь, чтобы быть названным объектно-ориентированным. Rust реализован под влиянием различных парадигм программирования.

Объекты содержат данные и поведение

Книга "Приёмы объектно-ориентированного проектирования. Паттерны проектирования" (1994) называемая также "книгой банды четырёх" является каталогом объектно-ориентированных шаблоны проектирования. В ней есть описание объектно-ориентированных программ:

Объектно-ориентированные программы состоят из объектов. Объект является пакетом состоящих из данных и процедур, которые работают с этими данными. Эти процедуры обычно называются методами или операциями.

В соответствии с этим определением Rust является объектно-ориентированным ЯП: структуры и перечисления имеют данные и блок impl, которые предоставляет методы для структур и перечислений. Хотя структуры и перечисления имеющие методы не называются объектами, они обеспечивают одинаковую функциональность, которую предоставляют объекты, используя определение объектов в книге банды четырёх.

Скрытие деталей реализации

Другим аспектом, обычно связанным с объектно-ориентированным программированием, является идея инкапсуляции: детали реализации объекта недоступны для кодирования с использованием этого объекта. Единственный способ взаимодействия с объектом - через публичный API, который предлагает объект; код с использованием объекта не должен охватить внутренние объекты объекта и напрямую изменить данные или поведение. Инкапсуляция позволяет изменять и реорганизовывать внутренние содержание объекта без необходимо изменить код, который использует объект.

Как мы обсуждали в главе 7, мы можем использовать ключевое слово pub, чтобы решить, какие модули, типы, функции и методы в нашем коде могут быть общедоступными. По умолчанию ни к каким элементам нет доступа. Например, мы можем определить структуру AveragedCollection, которая имеет поле, содержащее вектор значенийi32. Структура может также иметь поле, которое знает среднее значение в векторе так что всякий раз, когда кто-либо хочет знать среднее значение значений, имеет в своем векторе, нам не нужно его вычислять по требованию. AveragedCollection будет кэшировать рассчитанное среднее значение для нас. В коде 17-1 приведено определение структура AveragedCollection:

Filename: src/lib.rs


# #![allow(unused_variables)]
#fn main() {
pub struct AveragedCollection {
    list: Vec<i32>,
    average: f64,
}
#}

код 17-1: Структура AveragedCollection, которая содержит список целых чисел и среднее значение элементов в коллекция. </ SPAN>

Обратите внимание, что структура помечена pub, так что другой код может использовать её, но поля внутри структуры остаются закрытыми. Это важно, т.к.мы хотим гарантировать, что всякий раз, когда значение добавляется или удаляется из списка, мы также обновляем среднее значение. Мы делаем это, применяя методы add,remove и average в структуре, как показано в листинге 17-2:

Filename: src/lib.rs


# #![allow(unused_variables)]
#fn main() {
# pub struct AveragedCollection {
#     list: Vec<i32>,
#     average: f64,
# }
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 перестанет содержать актуальные данные. Метод average возвращает значение поля average, что позволяет внешнему коду прочитать average, но не изменять его.

Поскольку мы инкапсулировали детали реализации AveragedCollection, мы можем легко изменить такие аспекты, как структура данных в будущем. Например, мы могли бы использовать HashSet вместоVec для поля list. Т.к. методы add,remove и average общедоступны, код, использующий AveragedCollection, не нуждается в изменении. Такого у нас не получится, если мы сделали бы доступным поле list внешнему коду: HashSet иVec имеют разные методы для добавления и удаления элементов, поэтому внешний код, вероятно, должен будет измениться, если он изменяет list непосредственно.

Если инкапсуляция является обязательным аспектом для определения языка, как объектно-ориентированного, то Rust соответствует этому требованию. Модификатор доступа pub позволяет инкапсулировать детали реализации.

Наследование, как система типов и как способ совместно использования кода

Наследование - это механизм, который предоставляют некоторые языки программирования. Объект может быть определен, чтобы наследовать от определения другого объекта, тем самым получив данные и поведение родительского объекта без необходимости их повторного определения. Наследование является характеристикой объектно-ориентированного языка программирования.

Если язык должен реализовать наследование, чтобы быть объектно-ориентированным языком программирования, тогда Rust не является объектно-ориентированным. Не существует способа определить структуру, которая бы наследовала поля и методы от другой структуры. Однако, если вы привыкли использовать наследование, в Rust есть альтернативные решения.

У наследования есть два преимущества. Первое - это повторное использование кода. Реализации методов по умолчанию в типаже (листинг 10-15) даёт возможность использовать его в реализациях. Это похоже на то, как функционал родительского класса, наследуется его дочерними классами. Перезапись методов по умолчанию в реализациях также возможна.

Вторая причина использования наследования в системе типов - сообщить о том, что дочерний тип может быть использован в том же месте, что и родительский. Эта функциональная возможность также называется полиморфизм. Это когда множество объектов можеть быть использованы если они имеют соответствующую форму.

В то время как многие люди используют "полиморфизм" для описания наследования, это на самом деле определенный вид полиморфизма, называемая "полиморфизмом подтипа". Существуют другие формы. Использование обобщенного параметра в типаже, является также полиморфизмом, в частности «параметрический полиморфизм». Точный детали между различными видами полиморфизма здесь не имеют решающего значения, так что не беспокойтесь о деталях: просто знайте, что Rust имеет несколько функции, связанные с полиморфизмом, в отличие от многих языков ООП.

Чтобы поддерживать такой тип шаблона, у Rust есть типажи-объекты. Любое значение может подойти, если оно реализует определённый шаблон.

Наследование в последнее время не является излюбленным приёмом в различных языках программирования. Для повторного использования кода может понадобиться больше кода для совместного использования, чем вам действительно нужно. Подклассы не должны всегда используют все характеристики своего родительского класса, но наследование означает подкласс получает все данные и поведение своего родителя. Это может сделать дизайн программы менее гибкий и создает возможность вызова методов в подклассах, которые не имеют смысла или вызывают ошибки, поскольку методы не применяются к подклассу, но должны быть унаследованы от родительского класса. К тому же, некоторые языки позволяют только наследовать подкласс только от одного класса. Эти ограничения понижают гибкость дизайна программ.

Поэтому Rust выбрал использования типажей-объектов наследованию. В следующей секции вы узнаете, как типажи-объекты реализуют полиморфизм.