Начнем со структуры Client, которая содержит структуру verifier, помогающую нам выполнять проверку ответов, получаемых клиентом.

struct Verifier {}
impl Verifier {
    fn verify() {
        println!("Verification Successful!");
    }
}

pub struct Client {
    verifier: Verifier
}

Что не так с приведенной выше реализацией?

допустим, завтра мы хотим изменить реализацию того, как мы проверяем ответы в клиенте; это потребует от нас изменить структуру Verifier

Но мы также должны думать о тестировании? Как вы проводите модульное тестирование чего-то подобного? Мы не можем предоставить нашу фиктивную реализацию Verifier при тестировании.

Немного изменим реализацию

struct Verifier {}
impl Verifier {
    fn verify() {
        println!("Verification Successfull!");
    }
}
pub struct Client {
    verifier: Verifier
}
impl Client {
    fn constructor(verifier: Verifier) -> Self {
        return Client {
            verifier: verifier
        }
    }
}

Это ничего не меняет — мы внедряем Verifier в Client, но это все та же структура с той же реализацией verify() (поэтому поведение остается прежним).

Нам нужно что-то, что позволит нам настроить функцию verify()

Черты на помощь

Трейт позволяет нам определять общее поведение, определяя методы, которые может реализовать любой тип (даже те, которые вы не определили!)

Мы можем определить черту, которая включает поведение проверки

pub trait Verification {
    fn verify();
}

Теперь мы можем реализовать этот трейт для любого типа и передать его нашему конструктору Client, который позволяет нам создавать разные экземпляры Client с различным поведением проверки.

pub struct Client {
    verifier: Box<dyn Verification>
}
impl Client {
    fn constructor(verifier: Box<dyn Verification>) -> Self {
        return Client {
            verifier: verifier
        }
    }
}

Что это за Box‹dyn ..›?

Box помогает нам выделить память в куче, а затем помещает объект в блок в выделенной памяти.

Нам это нужно, потому что любой тип может реализовать трейт Verification, и компилятор не может знать размер переданного типа во время компиляции.

Теперь приведенная выше реализация позволяет нам передать любой тип, который реализует трейт Verification, который помогает при тестировании, а также позволяет разделить проблемы.

// Usage of Client Struct
pub struct FailingVerifier {}
impl Verification for FailingVerifier {
    fn verify() {
        println!("Failing Verification")
    }
}
fn main() {
    let client = Client::constructor(Box::new(FailingVerifier{}));
}

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