Factory pattern : What to do when factory class is too big?

1.2k views Asked by At

I am using entity-base-components system.

I have many type of stationary objects e.g.

  1. Wall = blocks
  2. Fire Turret = blocks + shooter
  3. Water Turret = blocks + shooter
  4. Bunker = blocks + spawner

Here is the factory of stationary objects :-

class StationaryObject{
    enum typeOfObject_enum{WALL,FIRE_TURRET, ....};
    Entity* create(typeOfObject_enum theType){ //it is enum
        switch(theType){
            case WALL: ... create some mesh, some physic body  ....
            case FIRE_TURRET: .... create some mesh, some physic body+ unique logic 20 lines ....
            ....
        }
    }
}

It works really good.

Question:
Now I want to create 100 types of Stationary objects, where should I store it?
Store all of them in class StationaryObjectwill make the class too big (?).

Note that there are tiny-but-unique logic in each type of object.

2

There are 2 answers

1
Chris Drew On

You could create a map from typeOfObject_enum to each object factory and then you can register factories in the map as you wish.

Each object factory could be something like astd::function<std::unique_ptr<Entity>()>:

enum class StationaryObjectType{WALL, FIRE_TURRET, WATER_TURRET};
const size_t STATIONARY_OBJECT_TYPE_COUNT = 3;
using EntityFactory = std::function<std::unique_ptr<Entity>()>;

class StationaryObjectFactory {
    std::array<EntityFactory, STATIONARY_OBJECT_TYPE_COUNT> factory_map; 
public:
    void registerFactory(StationaryObjectType type, EntityFactory factory){
        factory_map[static_cast<size_t>(type)] = std::move(factory); 
    }
    std::unique_ptr<Entity> create(StationaryObjectType type){
        auto factory = factory_map[static_cast<size_t>(type)];
        if (!factory)
            return nullptr;
        return factory();
    }
};

int main() {
    StationaryObjectFactory factory;

    // Register lambdas as the factory objects
    factory.registerFactory(StationaryObjectType::WALL, []{
        return std::make_unique<Wall>(); 
    });    
    factory.registerFactory(StationaryObjectType::FIRE_TURRET, []{
        return std::make_unique<FireTurret>(); 
    });

    auto wall = factory.create(StationaryObjectType::WALL);    
    auto fire_turret = factory.create(StationaryObjectType::FIRE_TURRET);
    auto water_turret = factory.create(StationaryObjectType::WATER_TURRET);

    assert(wall != nullptr);    
    assert(fire_turret != nullptr);
    assert(water_turret == nullptr);  // No WATER_TURRET factory registered
}

Live demo.

Or if you prefer, you could use implementations of an abstract factory class:

class EntityFactory {
public:
    virtual ~EntityFactory(){}
    virtual std::unique_ptr<Entity> operator()() = 0;
};

class WallFactory : public EntityFactory {
public:
    std::unique_ptr<Entity> operator()() override {
        return std::make_unique<Wall>();
    }
};

class FireTurretFactory : public EntityFactory {
public:
    std::unique_ptr<Entity> operator()() override {
        return std::make_unique<FireTurret>();
    }
};

Live demo.

0
Low Flying Pelican On

I have not used C++, but sounds like you can mix builder and factory patterns to get what you want,

class StationaryObject{
    Entity create(typeOfObject_enum theType){ //it is enum
        switch(theType){
            case WALL:
                return WallBuilder.Build();
            case FIRE_TURRET:
                return FireTurrentBuilder.Build();
            ....
        }
    }
}

You can optimize it by adding a base class BaseBuilder where you have any common logic to different Entities

class BaseBuilder<T> {
   Mesh CreateMesh(){...}

   ....

   T Build();
}

class WallBuilder : BaseBuilder<Wall> {
   Wall Build(){
   }
}

With this approach, if you want you can use a mapping between enum and builder, and get rid of case statement