Abstract Factory Pattern for Cross-Platform Hardware Abstraction
When engineering a cross-platform hardware abstraction layer, developers frequently encounter SDKs that diverge significantly across operating systems. Integrating multiple hardware vendors into a system that must support both Windows and Unix-like environments typically requires distinct wrapper classes for each OS-vendor combination.
Applying the Factory Method pattern initially addresses instantiation requirements. However, scaling this approach reveals structural redundancy: every new hardware brand necessitates duplicating the factory hierarchy across platforms. In practice, runtime deployments rarely mix platforms; an application compiled for Linux will consistently utilize Linux-specific wrappers for all connected peripherals. This constraint indicates that concrete products should be aggregated into platform-specific families rather than isolated per vandor.
The Abstract Factory pattern resolves this redundancy by defining a interface capable of producing entire suites of related objects. Instead of maintaining individual factories for each hardware brand, a single concrete factory per operating system generates all compatible device wrappers. The pattern decouples client logic from concrete implementations, relying exclusively on abstract product interfaces and factory definitions.
Pattern Architecture
The structure relies on four core components:
- Abstract Products: Interfaces defining the operational contract for each hardware type.
- Concrete Products: Platform-specific implementations satisfying the abstract contracts.
- Abstract Factory: Declares creation methods for every product type within a given family.
- Concrete Factories: Implement the creation methods to instantiate platform-matched concrete products.
Implementation
The following C++ implementation demonstrates how a unified factory per operating system manages the instentiation of multiple peripheral types.
#include <iostream>
// Abstract product interfaces
class IOpticalSensor {
public:
virtual ~IOpticalSensor() = default;
virtual bool Initialize() = 0;
virtual void CaptureFrame() = 0;
};
class IThermalSensor {
public:
virtual ~IThermalSensor() = default;
virtual bool Calibrate() = 0;
virtual float ReadTemperature() = 0;
};
// Linux concrete products
class LinuxOpticalDriver : public IOpticalSensor {
public:
~LinuxOpticalDriver() override = default;
bool Initialize() override { std::cout << "Linux optical init\n"; return true; }
void CaptureFrame() override { std::cout << "Capturing frame on Linux\n"; }
};
class LinuxThermalDriver : public IThermalSensor {
public:
~LinuxThermalDriver() override = default;
bool Calibrate() override { std::cout << "Linux thermal cal\n"; return true; }
float ReadTemperature() override { return 36.5f; }
};
// Windows concrete products
class WinOpticalDriver : public IOpticalSensor {
public:
~WinOpticalDriver() override = default;
bool Initialize() override { std::cout << "Windows optical init\n"; return true; }
void CaptureFrame() override { std::cout << "Capturing frame on Win\n"; }
};
class WinThermalDriver : public IThermalSensor {
public:
~WinThermalDriver() override = default;
bool Calibrate() override { std::cout << "Windows thermal cal\n"; return true; }
float ReadTemperature() override { return 37.0f; }
};
// Abstract factory
class ISensorFactory {
public:
virtual ~ISensorFactory() = default;
virtual IOpticalSensor* CreateOptical() = 0;
virtual IThermalSensor* CreateThermal() = 0;
};
// Concrete factories
class LinuxSensorFactory : public ISensorFactory {
public:
IOpticalSensor* CreateOptical() override { return new LinuxOpticalDriver(); }
IThermalSensor* CreateThermal() override { return new LinuxThermalDriver(); }
};
class WinSensorFactory : public ISensorFactory {
public:
IOpticalSensor* CreateOptical() override { return new WinOpticalDriver(); }
IThermalSensor* CreateThermal() override { return new WinThermalDriver(); }
};
// Client usage
int main() {
// Platform selection occurs at a single configuration point
ISensorFactory* hardwareKit = new LinuxSensorFactory();
IOpticalSensor* optics = hardwareKit->CreateOptical();
optics->Initialize();
optics->CaptureFrame();
IThermalSensor* thermal = hardwareKit->CreateThermal();
thermal->Calibrate();
float temp = thermal->ReadTemperature();
delete optics;
delete thermal;
delete hardwareKit;
return 0;
}
Trade-offs and Design Considerations
Advantages:
- Consistent Product Families: Guarantees that objects created by a factory belong to the same platform or variant family, preventing cross-platform API mixing.
- Simplified Configuration Switching: Changing the target environment requires modifying only the factory instantiation point, leaving downstream business logic untouched.
Disadvantages:
- Open-Closed Principle Violation on Product Expansion: Introducing a new peripheral type requires updating the abstract factory interface and modifying every existing concrete factory implementation.
- Platform Migration Friction: Switching runtime environments necessitates recompiling or reconfiguring the client code where the concrete factory is instantiated. In multi-client or distributed architectures, this modification must be propagated manually across all initialization routines.
The inherent rigidity regarding family extension often leads architects to supplement abstract factories with reflection, dependency injection containers, or configuration-driven instantiation strategies to maintain long-term flexibility.