In this lesson, you will learn how to design factory interfaces that leverage Rust’s trait system to inject dependencies into components at the moment of their creation.
Beyond Simple Constructors
When building large systems, a component often needs specific state—like a database connection, a configuration struct, or a logger—to function correctly. A robust factory doesn't just create an object; it orchestrates the assembly of these dependencies so the caller doesn't have to worry about the underlying wiring.
By using traits to define our factory interface, we can decouple the creation logic from the component implementation. This makes your code modular and allows you to swap out implementations (like a MockDatabase during testing) without changing your business logic.
Implementing Dependency Injection
The core idea is to define a trait that requires the necessary dependencies as arguments. This ensures that the factory has everything it needs to produce a fully functional component.
pub trait Component {
fn run(&self);
}
// A dependency our component needs
pub struct Database { /* ... */ }
pub trait ComponentFactory {
// The factory requires the dependency to be injected here
fn create(&self, db: Database) -> Box<dyn Component>;
}
pub struct UserComponent {
db: Database,
}
impl Component for UserComponent {
fn run(&self) { /* use self.db */ }
}
pub struct UserComponentFactory;
impl ComponentFactory for UserComponentFactory {
fn create(&self, db: Database) -> Box<dyn Component> {
Box::new(UserComponent { db })
}
}
Why This is Robust
By requiring the Database inside the create method, we create a type-safe contract. The factory cannot produce a UserComponent unless it possesses the required dependency. This shifts the burden of validation away from the object's constructor and into the factory’s assembly line.
Furthermore, because ComponentFactory is a trait, you can pass different factory implementations into your system’s initialization logic. If your system decides it’s time to move from a local file-based database to a cloud provider, you simply swap the factory implementation, and the rest of your system remains untouched.
Key Takeaway: A robust factory acts as an orchestrator, using trait-based interfaces to enforce dependency injection and guarantee that components are always initialized with valid state.