вторник, 14 января 2020 г.

Объекты в Rust. Часть 2.

Сегодня мы поговорим про полиморфизм в Rust. Полиморфизм в других языках базируется на таких свойствах объектно-ориентированного программирования, как наследование, абстрактные классы и виртуальные методы. Стоп, Rust не является объектно-ориентированным языком, более того наследование в нем отсутствует. Мода на наследование давно прошла и считается что использовать композицию в пользовательских типах лучше. Перед тем как двигаться дальше будет полезно прочитать статьи про Полиморфизм и Абстрактные классы (интерфейсы) в си++. Но с полиморфизмом в Rust все в порядке, есть и статический полиморфизм, и динамический полиморфизм. Полиморфное поведение в Rust обеспечивается с помощью “характеристик” (trait), которые напоминают интерфейсы из Java.

Классический полиморфизм обеспечивается отношением между родительским и наследуемым типом, когда мы можем ссылаться на наследника через родительский тип. Выборочно выполнять методы родителя или наследника с помощью кастования типов. Или можем создать абстрактный тип с виртуальными функциями для наследников, которые будут автоматически выбираться во время выполнения. Как это делается в Rust поговорим более подробно.

Статический полиморфизм - полиморфизм времени компиляции базируется на основе работы генериков и ручного кастования объектов в нужный тип. И так как в Rust нет наследования, родительский тип и его привязка к наследникам задается с помощью глобального типа Any (похоже на суперкласс Object из Java) или характеристик, которые мы будем рассматривать далее. При этом создаются копии методов с нужным типом при компиляции и это не требует дополнительных действий при выполнении, что повышает скорость работы программы.

Динамический полиморфизм - полиморфизм времени выполнения базируется на основе динамического преобразования типов во время выполнения. Задается с помощью ключевого слова “dyn” в определении ссылки на Any или характеристику. Так как выбор типа и определение нужного метода происходит во время выполнения приложения, это замедляет его работу. Учитывая, что в работе с характеристиками требуется динамическое кастование, которое тоже нагружает систему, можно отдать предпочтение удобству.

Итак, что же такое характеристика (trait)? Образно говоря, это гарантия способности выполнять определенные действия. В нашем контексте это гарантия наличия у типа определенных методов, которые мы можем использовать. Rust позволяет обращаться к типам, которые реализуют (impl - for) характеристику через тип характеристики (&dyn), как обращение к наследникам через базовый тип в ООП. Более того, мы можем цеплять характеристику к чужим (базовым в том числе) типам или требовать (where) от чужих типов ее наличие. Например, чтобы определить оператор "плюс" (+) для пользовательского типа, мы должны гарантировать определение метода add() из характеристики Add, которая находится в стандартной библиотеке (std::ops). Нужно отметить один момент, мы можем потребовать определить для характеристики тип(ы), с помощью type. Смотрим пример.

shape.rs
-----------
use std::ops::Add;

pub trait Drawable {
    fn draw(&self);
}

pub fn generic_draw<T>(value: T) where T: Drawable {
    value.draw();
}

#[derive(Debug)]
pub struct Point {
    pub x: i32,
    pub y: i32,
}

impl Add for Point{
    type Output = Point;

    fn add(self, rhs: Self) -> Self::Output {
        Point{
            x:self.x + rhs.x,
            y:self.y + rhs.y,
        }
    }
}

circle.rs
------------
use super::shape::*;

#[derive(Debug)]
pub struct Circle {
    radius: i32,
    center: Point,
}

impl Circle {
    pub fn new() -> Self {
        Circle {
            radius: 1,
            center: Point {
                x: 0,
                y: 0,
            },
        }
    }
}

impl Drawable for Circle {
    fn draw(&self) {
        println!("Draw circle: {:?}", self)
    }
}

triangle.rs
-------------
use super::shape::Drawable;

#[derive(Debug)]
pub struct Triangle {
    width: i32,
    heiht: i32,
}

impl Triangle {
    pub fn new() -> Self {
        Triangle {
            width: 5,
            heiht: 5,
        }
    }
}

impl Drawable for Triangle {
    fn draw(&self) {
        println!("Draw triangle: {:?}", self)
    }
}

main.rs
-----------
mod shape;
mod circle;
mod triangle;

use shape::*;
use circle::*;
use triangle::*;
use std::any::Any;

fn main() {
    let a = Point{x: 1, y: 1};
    let b = Point{x: 2, y: 2};
    let c = a + b;
    println!("Add operator: a + b = {:?}", c);

    let c1 = Circle::new();
    let t1 = Triangle::new();

    let objects: Vec<&dyn Any> = vec![&c1, &t1];
    for object in objects.iter() {
        if object.is::<Circle>() {
            println!("Circle:");
        } else if object.is::<Triangle>() {
            println!("Triangle:");
        }

        if let Some(circle) = object.downcast_ref::<Circle>() {
            circle.draw();
        } else if let Some(triangle) = object.downcast_ref::<Triangle>() {
            triangle.draw();
        }
    }

    //dynamic polymorphism
    let shapes: Vec<&dyn Drawable> = vec![&c1, &t1];
    for shape in shapes.iter() {
        shape.draw();
    }

    //static polymorphism
    generic_draw(c1);
    generic_draw(t1);
}

Перегуд В.

Комментариев нет:

Отправить комментарий