2

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.

javaLover
  • 6,347
  • 2
  • 22
  • 67
  • I would seriously recommend you switching to the newest c++ standard. use `enum class` instead of `enum`, return `std::unique_ptr` instead of `Entity*` – Jan Hohenheim Sep 01 '16 at 08:42
  • 2
    why do you even need a factory? – David Haim Sep 01 '16 at 08:54
  • factory = for convenience , I can call "create(WALL)" ; friendly for any wrapper .... should not I use it? – javaLover Sep 01 '16 at 08:55
  • 3
    and why is it any better than `std::make_unique`? I personally think that factory really solves nothing, it comes from the evil land of Java. before you know it, your code is bombed with hundreds of factories, builders, abstract classes and other kind of evil, unnecessary junk. – David Haim Sep 01 '16 at 08:56
  • `WallType` is not type in sense of C++, it is type of game object. A game object composes of many components. Here component is a `type` that can appear in `std::make_unique<>`. – javaLover Sep 01 '16 at 08:59
  • 3
    "WallType is not type in sense of C++, it is type of game object." well, it sounds that it should be a C++ type as well. here, I solved your problem – David Haim Sep 01 '16 at 09:01
  • It somehow make sense, thank. I will try to think in that way. I might obey the rule of "component-based" too much. – javaLover Sep 01 '16 at 09:10
  • 3
    @DavidHaim I agree with you that a factory doesn't make sense in this case; however, saying that "factory really solves nothing" is going too far. A factory let's you control what implementation gets instantiated at run time. A factory allows you to put away all the decision making of what implementation to use in a single class. A factory allows you to wire your objects based on runtime parameters... I could go on but you get the point. – Chetan Kinger Sep 01 '16 at 09:29

2 Answers2

4

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.

Chris Drew
  • 14,926
  • 3
  • 34
  • 54
2

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

Low Flying Pelican
  • 5,974
  • 1
  • 32
  • 43