Factory Pattern in Rust
https://en.wikipedia.org/wiki/Factory_method_pattern
Let’s look at another design pattern – “The factory pattern”
If you like these articles, there is a great site where you can learn more about Design Patterns : https://refactoring.guru/
The factory pattern is a design pattern in programming that helps create objects (or instances of classes) without telling the code exactly which kind of object to make.
Think of it like a factory in real life: a factory can make different types of products, but you don’t have to worry about how each product is made inside. You just say, “I need a product,” and the factory takes care of the details.
Here’s a simple example to make it clearer:
Factory Pattern
Imagine you’re in a video game where you can spawn different types of enemies: maybe a “Zombie,” a “Dragon,” or a “Robot.” You could have a bunch of code that manually creates each enemy, but that would get messy and hard to manage if you add more types later.
Instead, you can use a factory to do this for you. You just tell the factory, “Hey, I need an enemy,” and pass in what type of enemy you want. The factory takes care of creating the correct enemy without you needing to know how it works behind the scenes.
In simple terms:
- Without a factory: You do all the work of making each different enemy.
- With a factory: You let the factory do the work, so it’s easier to add new enemy types later.
This helps keep your code organized, flexible, and easier to maintain, especially when you want to add or change things later.
Let’s implement a factory pattern in Rust, using the example of spawning different types of enemies in a video game: Zombie, Dragon, and Robot.
Factory Pattern in Rust
Step 1: Define an Enemy
Trait
We’ll create a common trait Enemy
that all types of enemies will implement. The trait will have a attack
method to define each enemy’s attack style.
trait Enemy {
fn attack(&self) -> String;
}
Step 2: Define the Enemy Types
Next, we define three types of enemies: Zombie
, Dragon
, and Robot
, each implementing the Enemy
trait.
struct Zombie;
impl Enemy for Zombie {
fn attack(&self) -> String {
"Zombie attacks with a bite!".to_string()
}
}
struct Dragon;
impl Enemy for Dragon {
fn attack(&self) -> String {
"Dragon breathes fire!".to_string()
}
}
struct Robot;
impl Enemy for Robot {
fn attack(&self) -> String {
"Robot fires lasers!".to_string()
}
}
Step 3: Create the Factory Function
Now, let’s create a factory function that will return the correct type of enemy based on the input.
enum EnemyType {
Zombie,
Dragon,
Robot,
}
fn enemy_factory(enemy_type: EnemyType) -> Box<dyn Enemy> {
match enemy_type {
EnemyType::Zombie => Box::new(Zombie),
EnemyType::Dragon => Box::new(Dragon),
EnemyType::Robot => Box::new(Robot),
}
}
Explanation:
- Enum
EnemyType
: Used to decide which type of enemy to create. enemy_factory
function: Takes anEnemyType
and returns a boxedEnemy
object. In Rust,Box<dyn Enemy>
allows us to store and return any type that implements theEnemy
trait, making it possible to return different types from the factory.
Step 4: Using the Factory
Now we can use the factory to spawn different types of enemies and make them attack.
fn main() {
let zombie = enemy_factory(EnemyType::Zombie);
let dragon = enemy_factory(EnemyType::Dragon);
let robot = enemy_factory(EnemyType::Robot);
println!("{}", zombie.attack());
println!("{}", dragon.attack());
println!("{}", robot.attack());
}
Output:
Zombie attacks with a bite!
Dragon breathes fire!
Robot fires lasers!
Full Code:
trait Enemy {
fn attack(&self) -> String;
}
struct Zombie;
impl Enemy for Zombie {
fn attack(&self) -> String {
"Zombie attacks with a bite!".to_string()
}
}
struct Dragon;
impl Enemy for Dragon {
fn attack(&self) -> String {
"Dragon breathes fire!".to_string()
}
}
struct Robot;
impl Enemy for Robot {
fn attack(&self) -> String {
"Robot fires lasers!".to_string()
}
}
enum EnemyType {
Zombie,
Dragon,
Robot,
}
fn enemy_factory(enemy_type: EnemyType) -> Box<dyn Enemy> {
match enemy_type {
EnemyType::Zombie => Box::new(Zombie),
EnemyType::Dragon => Box::new(Dragon),
EnemyType::Robot => Box::new(Robot),
}
}
fn main() {
let zombie = enemy_factory(EnemyType::Zombie);
let dragon = enemy_factory(EnemyType::Dragon);
let robot = enemy_factory(EnemyType::Robot);
println!("{}", zombie.attack());
println!("{}", dragon.attack());
println!("{}", robot.attack());
}
Summary:
- We use the factory pattern to abstract away the creation of different enemy types.
- The
enemy_factory
function can easily be expanded to support new enemies by adding new cases to theEnemyType
enum and implementing theEnemy
trait for them.
Factory Pattern in Rust – example 2
#![allow(unused)]
trait Sounds {
fn make_sound(&self);
}
enum Pet {
Cat,
Mouse,
}
struct Cat;
struct Mouse;
impl Sounds for Cat {
fn make_sound(&self) {
println!("{:?}", "miaow");
}
}
impl Sounds for Mouse {
fn make_sound(&self) {
println!("{:?}", "eek");
}
}
fn pet_factory(input: Pet) -> Box<dyn Sounds> {
match input {
Pet::Cat => Box::new(Cat),
Pet::Mouse => Box::new(Mouse),
}
}
fn main() {
let pet1 = pet_factory(Pet::Cat);
let pet2 = pet_factory(Pet::Mouse);
pet1.make_sound();
pet2.make_sound();
}