Geant4 detector construction interface

Dear Geant4 users,
As you know, Geant4 provides a single class, G4VUserDetectorConstruction, to construct the entire detector. In my opinion, this is sufficient for a simple detector setup, but implementing a single class for building a complex detector results in complicated code and is not a good practice for writing clean code. To solve this issue, many g4 tutorials recommend splitting the implementation into additional methods or classes. But, as far as I know, there is no standard solution, and it is entirely left to the users’ ability.

For a clean implementation structure, I propose to use two more abstract class templates along with the G4VUserDetectorConstruction. Of course, many solutions can be suggested for this, but I think this solution can be useful for users as it is simple and in line with the G4 spirit.
The below figure shows UML diagram of the provided template.
Fig1.pdf (133.9 KB)

Examples:

//Only relevant sections are displayed.
//Creating a concrete logical volume builder class
class UserLVBuilder : public VComponentBuilder<G4LogicalVolume> {
	protected:  
    virtual G4LogicalVolume* Build() override;
};
G4LogicalVolume* UserLVBuilder::Build()
{
//Implementation..
}
//Creating a concrete physical volume builder class
class UserPVBuilder : public VComponentBuilder<G4VPhysicalVolume> {
	protected:  
    virtual G4VPhysicalVolume* Build() override;
};
G4VPhysicalVolume* UserPVBuilder::Build()
{
//Implementation..
}
//Creating a concrete logical volume factory class. This class can be made singleton.
class UserLogicalVolumeFactory: public VComponentFactory<G4LogicalVolume>
{       
  protected:    
    virtual G4LogicalVolume* Create(const G4String& name) override;             
};
G4LogicalVolume* UserLogicalVolumeFactory::Create(const G4String& name)
{
	G4LogicalVolume* lv = G4LogicalVolumeStore::GetInstance()->GetVolume(name);
  if(lv) return lv;

  if(name=="")
  {
  	UserLVBuilder1 builder;
  	return builder.GetProduct();
   
  }else if(name=="")
  {
  	UserLVBuilder2 builder;
  	return builder.GetProduct();
  }
  .
  .
  .
  else 
  {
    return nullptr; //the Get() method in the parent class handle nullptr (look at Fig. 1).
  }
   
}
//Getting desired component object from the user detector construction class.
class UserDetectorConstruction : public G4VUserDetectorConstruction{
	G4VPhysicalVolume* Construct() override;
};
G4VPhysicalVolume* DetectorConstruction::Construct()
{
	//G4VPhysicalVolume* pv = UserPhysicalVolumeFactory::GetInstance()->Get("..");	
	//G4LogicalVolume* pv = UserLogicalVolumeFactory::GetInstance()->Get("..");	
	UserLVBuilder1 builder;
	G4LogicalVolume* lv = builder.GetProduct();
  //code for final detector construction 
}

Maybe there are much better solutions in the G4 examples. If so, I would like to know which example.