Factory, managers, interfaces for noobs?

Started by
6 comments, last by frob 9 years ago

Hi,

I have been looking through some source code of 'simple' game projects and I keep seeing these words pop up:

something Factory e.g AnimationFactory.

something that inherits from an interface e.g. class pickupBox : public ICreatable

something Manager e.g. GameManager

I have goggled these terms and find a lot of articles discussing quite specific cases, or explaining them in a way I cant understand. I really need an explanation that focuses on their purpose for game engine development.

Can someone please explain, or link to good simple articles, that explain what these things are, and why they are useful? For instance are they to do with OOP, or a general software engine architectural pattern?

Thanks for your time.

Advertisement
I don't have any articles on hand, but maybe I can give you a quick overview that will help.

Factory: This is something that will create objects for you of a certain type based on some identifying piece of information.

Example:

std::string monsterToCreate = mapFile.ReadString();
std::unique_ptr<Monster> newMonster = MonsterFactory.CreateMonster(monsterToCreate);
This way I don't have to hard-code what kinds of monsters are in a map in my game, but it can make the monsters I want from a list of known types given a string (or enum value, or whatever you want to use).

Interface: An "interface" is used in object-oriented programming to tell code what kinds of functions an object supports, without having any data or function implementations (in fact, some languages like C# won't even let you have data or function implementations in an interface class).

Example:

class IUpdatable
{
public:
  virtual Update(float aDelta) = 0;
  // No implementation of the function

protected:
  // Prevent deletion via a pointer to this interface type
  ~IUpdatable() {}

  // No data
};

class Bullet: public IUpdatable
{
public:
  virtual Update(float aDelta) override { /* do stuff */ }

  // Anything else I want bullets to do here
};

void UpdateGame(float aDelta)
{
  std::vector<IUpdatable*> updatableObjects = GetUpdatableObjects();
  for (auto object : updatableObjects)
  {
    // The IUpdatable interface says I can call Update on everything - so do so
    // I don't have to care if the object is a bullet or a monster, or whatever
    object->Update(aDelta);
  }
}
Manager: Almost always an anti-pattern. People like to make these to "manage" things, usually assets, but they are almost always better off splitting off the responsibilities into sub classes. For example, a "resource manager" is probably better as a resource loader (that can load files from disk) and a resource cache (that holds all loaded resources and can return an already loaded resource).

I think that this is a good resource for programmers new to design patterns. There aren't distinct sections for the particular patterns you are asking about, but managers and factories do come up in various places.

I also think it's important to point out that they aren't exactly pieces of game engines so much as they are solutions to common programming problems. It isn't that game engines use a pattern in scenarios A, B, and C, but rather that games need to execute tasks often have needs that are met by patterns, like any other software.

For your specific questions, SmkViper has already provided good answers.

-------R.I.P.-------

Selective Quote

~Too Late - Too Soon~


I have been looking through some source code of 'simple' game projects and I keep seeing these words pop up:

The main advices I have from the top of my head are:

  • #1 Single Responsability Principle (SRP)
  • #2 Components over inheritance

#1 is mandatory.

#2 means good code maintence.


For instance are they to do with OOP, or a general software engine architectural pattern?

On event based applications, a lot of design patterns applies correctly to it, but a game simulation it is different because it is run-time application. IMHO, the state pattern is used a lot in games.


something Manager e.g. GameManager

Do not create a class called Manager and another SomethingManager class extending it. Use the the SomethingManager class directly. In this case is the same thing of creating a class called CClass and extending it. Not every class is an interface; review your needs. To help you defining what are the classes that needs interfaces, use #1.

@SmkViper

Thanks for your post. Going back through some of the code with your information at hand is helping me get a grip on these concepts.

One thing I don't fully understand, in regards to this:

std::vector<IUpdatable*> updatableObjects = GetUpdatableObjects();
for (auto object : updatableObjects)
{
// The IUpdatable interface says I can call Update on everything - so do so
// I don't have to care if the object is a bullet or a monster, or whatever
object->Update(aDelta);
}

Is it basically saying that if we inherit a class from IUpdatable, chances are we want it to update in the update loop. So rather than keeping a list of all things we want to update, make them derive from IUpdatable? So does this bit: std::vector<IUpdatable*> updatableObjects = GetUpdatableObjects(); update all of the classes that derive from IUpdatable?

And in response to managers, are you saying that it is basically a bit vague to call something a manager, since in order for it to 'manage' something it has to perform an actual operation (like the ones u mentioned load from disk, store in memory). Therefore, we may as well call it by the operations it does to make the code cleaner and more understandable?

@Khaiy That is a great way of putting it. I could not see where they fit in to the grand scheme of things but "design patterns" makes sense.

@Irlan So these "patterns" basically are a good way of controlling game state?

"Do not create a class called Manager and another SomethingManager class extending it. Use the the SomethingManager class directly."

So basically what SmkViper says, whatever the SomethingManager uses, call it that instead?

@SmkViper

Thanks for your post. Going back through some of the code with your information at hand is helping me get a grip on these concepts.

One thing I don't fully understand, in regards to this:



std::vector<IUpdatable*> updatableObjects = GetUpdatableObjects();
for (auto object : updatableObjects)
{
// The IUpdatable interface says I can call Update on everything - so do so
// I don't have to care if the object is a bullet or a monster, or whatever
object->Update(aDelta);
}



Is it basically saying that if we inherit a class from IUpdatable, chances are we want it to update in the update loop. So rather than keeping a list of all things we want to update, make them derive from IUpdatable? So does this bit: std::vector<IUpdatable*> updatableObjects = GetUpdatableObjects(); update all of the classes that derive from IUpdatable?


It was mostly a simple example I could pull out off the top of my head that didn't (obviously) fall into the pitfalls of most OOP programming that tries to tell you squares are special rectangles. (aka, violating the substitution principle)

The point I was trying to get across is an interface defines a task or a set of tasks that an object can perform on behalf of someone else. And that someone else doesn't have to care what exact object it is - just that it can do what it asks. So the update loop doesn't care what objects it is running over, just that it is given a list of objects that can be updated.

To stretch the analogy a little - a keyboard is an interface to a computer. Your fingers don't have to care whether the computer has an Intel CPU, or is an Android phone. As long as the keys produce letters when pressed and are in the standard layout for your language, you can use it smile.png

And in response to managers, are you saying that it is basically a bit vague to call something a manager, since in order for it to 'manage' something it has to perform an actual operation (like the ones u mentioned load from disk, store in memory). Therefore, we may as well call it by the operations it does to make the code cleaner and more understandable?

@Khaiy That is a great way of putting it. I could not see where they fit in to the grand scheme of things but "design patterns" makes sense.

@Irlan So these "patterns" basically are a good way of controlling game state?
"Do not create a class called Manager and another SomethingManager class extending it. Use the the SomethingManager class directly."

So basically what SmkViper says, whatever the SomethingManager uses, call it that instead?


Almost. The main problem with "Manager" classes is they violate the Single Responsibility Principle. A class or function or other unit of processing should do one thing, and do that one thing well. If it does more then one thing then it starts being more difficult to understand and test. Another problem that managers have - though it's not unique to them - is a lot of people like to make them singletons which is another (generally) bad programming practice - as they're essentially globals in OOP clothing. Globals are frowned upon because they hide dependencies and make code hard to test (as it is hard to find all the globals code uses so you can properly mock them for testing).

In short - if you want a "manager" class, try to write down exactly what you want it to do and see if you can break it up into smaller independent parts. To use the "resource manager" example again, if you really do want something that can load an asset file on demand or provide it from a cache if it has already been loaded, then write a separate class/function to do that. Then implement it in terms of a loader and a cache object/class instead of trying to get it to do both tasks on it's own. Not only does that make it easier to test (because you're only testing the load or cache logic) but if you decide you want to use a different loader or different caching mechanism they can easily be swapped out without writing your picker logic. (Or even swap out the "cache or load" logic without touching your cache or loader code)

@Irlan So these "patterns" basically are a good way of controlling game state?
"Do not create a class called Manager and another SomethingManager class extending it. Use the the SomethingManager class directly."

So basically what SmkViper says, whatever the SomethingManager uses, call it that instead?

Yes. But...

IMHO, there is no problem calling a class "Manager" if its functionality it is well defined. For that, you to follow the SRP.

Follow the SRP;

Follow the SRP;

Follow the SRP;

...

"Software engineering is not hard for who follows the SRP".

As an example, check this link. There are lot of managers on it. On this case I don't agree. The probability of calling a class "manager", is small. This happens when you need an interface that connects a lot of objects related to an common class in a point that over-engineering doesn't makes sense. In design patterns terms this would be called the facade pattern.

It is not so much that a somethingManager is inherently bad, the problem is that the name is not distinctive, not descriptive.

It tends to quickly acquire secondary functionality, and tertiary functionality, and before long you discover that there are some global static instances of the object that many classes cannot live without, and it extends program startup for many seconds as you wait for all your managers to build complex systems before main() is even called....

Critically: What exactly is a "manager"? Remembering the Single Responsibility Principle, what single responsibility is covered in "manage"?

It generally depends on what is getting "managed". Often a "manager" is a group of items, this may be better termed a "cache", "pool", "store", "collection", or similar. Often they will have other responsibilities as well, such as loading data, saving data, queuing actions, unloading resources, or whatever else the person thought "manage" means.

The most cited type of "manager" people in games love to pull out is a resource manager. Normally for me I consider that four classes: A "store" that serves as the central hub. The store returns a "proxy", which may have the live and loaded values or may have fake values, but either way it will behave reasonably. The store has a "cache" that acts as the backing for data to a live proxy, and the store has a "loader" that manages getting data from storage/memory/network and into the cache. Again: FooStore, FooProxy, FooCache, FooLoader.

Similarly, someone may want to build a "ConnectionManager". But since "manage" is a terribly generic word, I'll look over their code and see if they meant to create a "ConnectionPool".

Other times they'll create what they call a manager, but on reviewing the class I'll discover it is really a collection.

The biggest problem is that these "manager" classes very often turn into a God class. They know too much about too many things, and very quickly spiral out of control into mega-classes that are difficult to maintain, debug, extend.

Thankfully so many tools support an automated class rename across the entire app and even between multiple apps, and it is easy to rely on your compiler to help in the process. Figure out the primary responsibility, rename it to match that responsibility. Anything that no longer fits the proper responsibility gets pulled out into its own block of code, also with its own single responsibility.

This topic is closed to new replies.

Advertisement