Improving Abstract Factory with Simple Factory
The initial implementation of abstract factory pattern presents two major issues: client code violates the open-closed principle, and provider code also breaches this principle. To address the first issue, we can apply simple factory concepts to refactor the abstract factory approach.
Instead of maintaining separate factory classes like CameraFactory, BaslerCameraFactory, and SickCameraFactory, we introduce a unified SimpleFactory class.
// Factory implementation
class SimpleFactory
{
public:
BaslerCamera* CreateBaslerCamera()
{
if ("Linux" == platform_)
{
return new LinuxBaslerCamera();
}
else if ("Windows" == platform_)
{
return new WindowsBaslerCamera();
}
else
{
return nullptr;
}
}
SickCamera* CreateSickCamera()
{
if ("Linux" == platform_)
{
return new LinuxSickCamera();
}
else if ("Windows" == platform_)
{
return new WindowsSickCamera();
}
else
{
return nullptr;
}
}
std::string platform_ = "Linux";
};
// Client usage
int main()
{
SimpleFactory* factory = new SimpleFactory();
BaslerCamera* basler = factory->CreateBaslerCamera();
basler->OpenCamera();
SickCamera* sick = factory->CreateSickCamera();
sick->OpenCamera();
return 0;
}
While this approach resolves the client-side violation, the provider-side issue persists.
Registry-Based Enhancement
The core problem with provider code is that adding new products requires modifications to the factory class, violating the open-closed principle. To eliminate the conditional branching logic and reduce coupling, we can implement a registry mechanism similar to what's used in factory method patterns.
Product registration can be implemented as follows:
class LinuxBaslerCamera : public BaslerCamera
{
public:
~LinuxBaslerCamera() override = default;
bool OpenCamera() override
{
return true;
}
};
ReflectRegister("LinuxBasler", LinuxBaslerCamera);
With the registry in place, the factory implementation becomes:
class SimpleFactory
{
public:
BaslerCamera* CreateBaslerCamera()
{
std::string identifier = platform_ + "Basler";
return Object::CreateObject<BaslerCamera>(identifier);
}
SickCamera* CreateSickCamera()
{
std::string identifier = platform_ + "Sick";
return Object::CreateObject<SickCamera>(identifier);
}
std::string platform_ = "Linux";
};
When extending to new product families like HarmonyOS, simply register the corresponding product classes using ReflectRegister and udpate the platform_ value. Ideally, this platform identifier should be loaded dynamically from configuration files at runtime.
However, when adding new products within existing families (such as introducing a Huaray camera), the factory class still requires a new creation method like CreateHuarayCamera().