Course contentsShow
← Rust
Lesson 664 of 1,9105. Type System and TraitsFree lesson

Designing Robust Component Factories

Create factory interfaces that support dependency injection of required state.

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.