У вас нет доступа к конфигурации библиотеки извне.
Вы никогда не сможете узнать конкретные типы бэкэндов из потребительского кода, поэтому вам придется придумать какой-то механизм, чтобы иметь возможностьчтобы создать их, принимая во внимание различные потребности каждого из их конструкторов.
Основная идея здесь состоит в том, чтобы ввести контекст, что-то вроде контекста внедрения зависимости, который вы можете использовать в объектно-ориентированном языке.Контекст содержит значения, которые могут понадобиться конструктору.
Для создания объектов признаков вам нужна черта:
pub trait Backend {
// all the common stuff for backends
}
Черта для построения бэкэндов и структура для хранения всехвозможные переменные конфигурации, необходимые для этих бэкэндов.Это не может быть той же чертой, что и Backend
, потому что метод new
не позволяет превратить его в объект.Большинство переменных являются необязательными, так как не все бэкэнды нуждаются в них:
pub trait BackendContstruct {
fn new(ctx: &BackendContext) -> Result<Box<Backend>, BackendError>;
}
pub struct BackendContext<'a> {
var_1: Option<&'a str>,
var_2: Option<&'a str>,
another: Option<bool>,
// etc
}
Если вы предоставите неправильные переменные, то вам необходимо вернуть ошибку.К сожалению, динамическое построение означает, что ошибки выполняются во время выполнения, а не во время компиляции:
pub struct BackendError(String);
Доступность каждого бэкэнда зависит от поддержки платформы.Поэтому сделайте их определения зависимыми от платформы:
#[cfg(platform1)]
mod backend1 {
pub struct Backend1;
impl ::Backend for Backend1 {}
impl ::BackendContstruct for Backend1 {
fn new(ctx: &::BackendContext) -> Result<Box<::Backend>, ::BackendError> {
if ctx.var_1.is_none() {
Err(::BackendError("Backend1 requires val_1 to initialize".to_string()))
} else {
Ok(Box::new(Backend1 {}))
}
}
}
}
#[cfg(platform1)]
#[cfg(platform2)]
mod backend2 {
pub struct Backend2;
impl ::Backend for Backend2 {}
impl ::BackendContstruct for Backend2 {
fn new(ctx: &::BackendContext) -> Result<Box<::Backend>, ::BackendError> {
Ok(Box::new(Backend2 {}))
}
}
}
Ни один из конкретных типов не является общедоступным, и любой из них может просто не существовать.Поэтому предоставьте перечисление, чтобы потребители могли указать, какой сервер они хотят:
pub enum BackendType {
// these names are available in all configurations
Default, Backend1, Backend2, Backend3
}
И функцию для создания серверов.Это будет Err
, чтобы запросить неподдерживаемый бэкэнд или пропустить необходимые переменные в контексте.Потребителей следует поощрять к использованию варианта Default
, который должен иметь действительный бэкэнд на любой платформе:
pub fn create_backend(backend: BackendType, ctx: &BackendContext) -> Result<Box<Backend>, BackendError> {
match backend {
#[cfg(platform1)]
#[cfg(platform2)]
BackendType::Default => Backend2::new(ctx),
#[cfg(platform1)]
BackendType::Backend1 => Backend1::new(ctx),
#[cfg(platform1)]
#[cfg(platform2)]
BackendType::Backend2 => Backend2::new(ctx),
_ => Err(BackendError("Backend not available".to_string()))
}
}