0% found this document useful (0 votes)
8 views9 pages

Design Pattern

This guide outlines essential C++ design patterns for developing modular and scalable systems in machine learning and autonomous vehicles. It covers patterns such as Strategy, Factory, Abstract Factory, Observer, Singleton, Adapter, Decorator, and Facade, providing code examples and typical use cases for each. The document serves as a comprehensive reference for implementing these patterns effectively in ADAS and ML systems.

Uploaded by

sriharsha.mldl
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views9 pages

Design Pattern

This guide outlines essential C++ design patterns for developing modular and scalable systems in machine learning and autonomous vehicles. It covers patterns such as Strategy, Factory, Abstract Factory, Observer, Singleton, Adapter, Decorator, and Facade, providing code examples and typical use cases for each. The document serves as a comprehensive reference for implementing these patterns effectively in ADAS and ML systems.

Uploaded by

sriharsha.mldl
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

C++ Design Patterns for ADAS/ML Systems

This guide covers the top C++ design patterns essential for building modular, scalable, and testable systems in
machine learning, computer vision, ADAS, and autonomous vehicles. Each pattern includes real-world relevance,
code examples, and when to use them.

1. Strategy Pattern - Swappable Algorithms


What It Is
Encapsulates interchangeable algorithms behind a common interface. Clients choose one at runtime.
When to Use

• Sensor fusion (radar-first vs. camera-first)


• Object tracking (e.g., Kalman vs. particle filter)
Code Example
class FusionStrategy {
public:
virtual void fuse() = 0;
virtual ~FusionStrategy() = default;
};

class RadarFirstFusion : public FusionStrategy {


public:
void fuse() override { std::cout << "Radar-first fusion\n"; }
};

class CameraFirstFusion : public FusionStrategy {


public:
void fuse() override { std::cout << "Camera-first fusion\n"; }
};

class FusionSystem {
FusionStrategy* strategy_;
public:
void setStrategy(FusionStrategy* strategy) { strategy_ = strategy; }
void runFusion() { strategy_->fuse(); }
};
2. Factory Pattern - Encapsulated Object Creation
What It Is
Creates objects without exposing the instantiation logic.
When to Use

• Choose backend engine (ONNX, TensorRT)


• Modularize loggers, models, or data sources
Code Example
class ModelRunner {
public:
virtual void run() = 0;
};

class TensorRTRunner : public ModelRunner {


public:
void run() override { std::cout << "Running with TensorRT\n"; }
};

class ONNXRunner : public ModelRunner {


public:
void run() override { std::cout << "Running with ONNX\n"; }
};

class ModelFactory {
public:
static std::unique_ptr<ModelRunner> create(const std::string& type) {
if (type == "tensorrt") return std::make_unique<TensorRTRunner>();
if (type == "onnx") return std::make_unique<ONNXRunner>();
return nullptr;
}
};
3. Abstract Factory - Families of Related Objects
What It Is
Creates families of related components that work together, abstracting away specific details.
When to Use

• Create platform-specific stacks (e.g., Orin vs. x86)


• Ensure compatibility between components
Code Example
class PreProcessor {
public:
virtual void preprocess() = 0;
};

class CUDAProcessor : public PreProcessor {


public:
void preprocess() override { std::cout << "CUDA preprocessing\n"; }
};

class CPUProcessor : public PreProcessor {


public:
void preprocess() override { std::cout << "CPU preprocessing\n"; }
};

class InferenceFactory {
public:
virtual std::unique_ptr<ModelRunner> createRunner() = 0;
virtual std::unique_ptr<PreProcessor> createPreproc() = 0;
};

class NvidiaFactory : public InferenceFactory {


public:
std::unique_ptr<ModelRunner> createRunner() override {
return std::make_unique<TensorRTRunner>();
}
std::unique_ptr<PreProcessor> createPreproc() override {
return std::make_unique<CUDAProcessor>();
}
};
4. Observer Pattern - Event Notification System
What It Is
One-to-many relationship where subjects notify observers about state changes.
When to Use

• Broadcast sensor frame updates


• Log or visualize new data frames
Code Example
class Observer {
public:
virtual void onFrame() = 0;
};

class FrameProvider {
std::vector<Observer*> observers;
public:
void add(Observer* obs) { observers.push_back(obs); }
void newFrame() {
for (auto* obs : observers) obs->onFrame();
}
};

class Logger : public Observer {


void onFrame() override { std::cout << "Logging frame\n"; }
};
5. Singleton Pattern - Single Instance Global Access
What It Is
Ensures only one instance of a class exists globally.
When to Use

• GPU or memory context


• Global configuration or logging state
Code Example
class GPUContext {
private:
static GPUContext* instance;
GPUContext() { std::cout << "GPU context initialized\n"; }

public:
static GPUContext* get() {
if (!instance) instance = new GPUContext();
return instance;
}
void allocate() { std::cout << "Allocating GPU resources\n"; }
};
GPUContext* GPUContext::instance = nullptr;
6. Adapter Pattern - Legacy Code Integration
What It Is
Translates one interface into another to allow reuse of legacy or third-party code.
When to Use

• Wrap legacy CUDA kernels or 3rd-party APIs


Code Example
class IKernel {
public:
virtual void run() = 0;
};

class LegacyKernel {
public:
void executeLegacy() { std::cout << "Legacy CUDA kernel\n"; }
};

class LegacyAdapter : public IKernel {


LegacyKernel legacy;
public:
void run() override { legacy.executeLegacy(); }
};
7. Decorator Pattern - Add Behavior Dynamically
What It Is
Dynamically adds new behavior to objects by wrapping them.
When to Use

• Add logging, profiling, or validation to a core object


Code Example
class IProcessor {
public:
virtual void process() = 0;
};

class CoreProcessor : public IProcessor {


public:
void process() override { std::cout << "Core processing\n"; }
};

class ProfilingDecorator : public IProcessor {


std::unique_ptr<IProcessor> wrapped;
public:
ProfilingDecorator(std::unique_ptr<IProcessor> w) : wrapped(std::move(w)) {}
void process() override {
auto start = std::chrono::high_resolution_clock::now();
wrapped->process();
auto end = std::chrono::high_resolution_clock::now();
std::cout << "[Profiling] Time: "
<< std::chrono::duration<double, std::milli>(end - start).count() << " ms\n";
}
};
8. Facade Pattern - Simplified High-Level Interface
What It Is
Hides system complexity by providing a unified interface to multiple subsystems.
When to Use

• Control an entire ADAS perception pipeline


Code Example
class PerceptionFacade {
SensorManager sensor;
Preprocessor preproc;
ModelRunner model;
PostProcessor postproc;

public:
void runPipeline() {
sensor.capture();
preproc.preprocess();
model.infer();
postproc.process();
std::cout << "Perception pipeline complete\n";
}
};
Summary Table

Pattern Purpose Typical Use Case

Strategy Choose behavior at runtime Fusion strategy, tracking method

Factory Create one object based on type Select inference backend

Abstract Factory Create group of related objects Platform-specific pipelines

Observer Notify many objects when event occurs Frame ready → trigger logging, fusion, etc.

Singleton One shared instance globally GPU context, config manager

Adapter Wrap legacy/3rd-party code Use old CUDA kernel in modern pipeline

Decorator Add behavior without changing original object Inject profiling/logging around core components

Facade Simplify interaction with complex subsystems Run full perception pipeline in one function

You might also like