Template Method Pattern
The Template Method Pattern defines the skeleton of an AI algorithm in a base class, letting subclasses override specific steps without changing the algorithm's structure.
Pattern Overview
This pattern defines the program skeleton of an algorithm in an operation, deferring some steps to subclasses while maintaining the overall algorithm structure.
Structure
// Abstract base trait for ML algorithms
pub trait MachineLearningAlgorithm {
// Template method that defines the algorithm skeleton
fn train(&self, data: &[f64]) -> String {
self.initialize();
let preprocessed = self.preprocess_data(data);
let model = self.build_model(&preprocessed);
let result = self.optimize_model(model);
self.finalize();
result
}
// Abstract methods to be implemented by subclasses
fn preprocess_data(&self, data: &[f64]) -> Vec<f64>;
fn build_model(&self, data: &[f64]) -> String;
fn optimize_model(&self, model: String) -> String;
// Hook methods with default implementations
fn initialize(&self) {
println!("Initializing algorithm...");
}
fn finalize(&self) {
println!("Algorithm completed.");
}
}
// Concrete implementation for Neural Network
pub struct NeuralNetwork {
layers: usize,
}
impl NeuralNetwork {
pub fn new(layers: usize) -> Self {
Self { layers }
}
}
impl MachineLearningAlgorithm for NeuralNetwork {
fn preprocess_data(&self, data: &[f64]) -> Vec<f64> {
println!("Neural network preprocessing...");
// Normalize data
data.iter().map(|x| x / data.len() as f64).collect()
}
fn build_model(&self, data: &[f64]) -> String {
println!("Building neural network with {} layers", self.layers);
format!("nn_model_{}_layers", self.layers)
}
fn optimize_model(&self, model: String) -> String {
println!("Optimizing neural network with backpropagation");
format!("optimized_{}", model)
}
fn initialize(&self) {
println!("Initializing neural network...");
println!("Setting up {} layers", self.layers);
}
}
// Concrete implementation for Decision Tree
pub struct DecisionTree {
max_depth: usize,
}
impl DecisionTree {
pub fn new(max_depth: usize) -> Self {
Self { max_depth }
}
}
impl MachineLearningAlgorithm for DecisionTree {
fn preprocess_data(&self, data: &[f64]) -> Vec<f64> {
println!("Decision tree preprocessing...");
// Simple discretization
data.iter().map(|x| x.round()).collect()
}
fn build_model(&self, data: &[f64]) -> String {
println!("Building decision tree with max depth {}", self.max_depth);
format!("dt_model_depth_{}", self.max_depth)
}
fn optimize_model(&self, model: String) -> String {
println!("Optimizing decision tree with pruning");
format!("pruned_{}", model)
}
fn initialize(&self) {
println!("Initializing decision tree...");
println!("Max depth set to {}", self.max_depth);
}
}Usage Example
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
// Use neural network algorithm
let nn = NeuralNetwork::new(3);
let nn_result = nn.train(&data);
println!("Neural network result: {}", nn_result);
// Use decision tree algorithm
let dt = DecisionTree::new(5);
let dt_result = dt.train(&data);
println!("Decision tree result: {}", dt_result);Benefits
- Code Reuse: Common algorithm structure is reused
- Consistency: Ensures all implementations follow the same pattern
- Flexibility: Easy to add new algorithm variants
- Control: Base class controls the algorithm flow
Use Cases
- Implementing different ML algorithms with common workflow
- Creating agent behavior templates with customizable steps
- Building data processing pipelines with variable components
- Standardizing agent lifecycle management