What we should use instead of a “manager” class in a good OOP design?
I'm having trouble designing my game engine. For example:
When I think in Resources, I think in a ResourceManager class to manage resources on my engine.
This class gets several responsibilities:
- Load resources.
- Map resources by an identifier.
- Ensure that resources are loaded only once.
- Free unused resources (using smartpointers).
This works for me, but I have read on every OOP design tutorial that managers are ambiguous classes with high coupling and low cohesion. I understand this, and I agree, but I searched almost the whole Internet to find a clear example of a real alternative to Managers but I didn't found it.
Some people explain, for example that a ResourceManager should be divided into smaller classes: A ResourceFactory, a ResourceCollection, a ResourceCache, a ResourceFacade...
I know all this design patterns (Factory, Collection, Facade, etc.) But I don't actually understand how this can be joined to create a (easy to manage) resource management system.
My question is: Is there some tutorial or document with a clear example? Can someone explain an example of this system? I'll thank you if you can write a small example in C++ or another similar language.
Thanks in advance for your help!
- Load resources -> ResourceLoader
- Map resources by an identifier -> ResourceMapper
- Ensure that resources are loaded only once -> CachedResourceLoader / this one uses the ResourceLoader when it doesn't already have it loaded
- Free unused resources (using smartpointers) -> ? / not sure about this one, but load+unload seems like highly cohesive concepts
re extra info in the comment about "Free unused resources":
First, let's clear that we only have 2 of the above involved: ResourceMapper and CachedResourceLoader. As the ResourceLoader instance is used from the CachedResource, it's not exposed to the rest of the code.
Note that the following depends on the domain knowledge, so I'll keep it open / you'll know which of these make sense / applies to the pieces you have. I'd say there are 3 options here:
- ResourceMapper uses CachedResourceLoader, and you only expose the earlier to the rest of the code.
- In this approach it there is a single entry point through ResourceMapper, so it makes sense it controls FreeUnused().
- Once it determines it can free a resource, it'll remove it from the map. If needed it'd call an Unload for specific items to the CachedResourceLoader
- Both CachedResourceLoader and ResourceMapper are used by the caller.
- If you only need to FreeUnused from the ResourceMapper, then just add it there.
- If it needs to be freed from both, one of:
- .FreeUnused in the ResourceMapper receives a cachedResourceLoader. After determining/removing the items from the map, it passes the list of resources to free to the cachedResourceLoader instance
- .FreeUnused in the ResourceMapper returns the list of resources freed from the map. The caller calls .Unload on the cachedResourceLoader passing the list of resources to Unload. Maybe you have a nice place in the caller these 2 calls would fit well.
- You do use a ResourceManager, but instead of having all the implementations in there, it uses the ResourceMapper and CachedResourceLoader. It'd be very thin, just doing calls to the others, in particular the .FreeUnused and .Unload calls in the last sub bullet above.
A final note: I think it's definitely worth it to separate the responsibilities and feel strong about doing so. That said, it's the SOLID principles I follow the most, I don't memorize pattern names and don't pay that much attention to rules like Manager classes are bad.
You almost said it already:
This class gets several responsibilities
This violates the Single Responsibility Principle. Additionally, the word Manager suggest an object that manages other objects, which is at odds with the object-oriented principle that objects should encapsulate both data and behavior. This quickly leads to the Feature Envy code smell.
Dividing the manager into many smaller classes is the correct thing to do. To keep them manageable, you can implement a Facade over them.
But I don't think that there is necessarily something wrong with your solution. IMHO if you think about stuff you said from Aspect Oriented Programming's point of view, it's just an aspect on your resources that will be handled by your class.
Although if you think in large and your code base may evolved in hundred of line of code, it will be better to break down your Infrastructure functions(Domain Driven Design Patterns).
I think your class is a cohesive one and the manager name is not always a bad sign except that it consolidates the control flow of many possibly unrelated classes that collaborate with each other to accomplish a task.
I comment that if your requirements about caching and mapping is prone to change maybe it will be better to Separate your Concerns.
You don't need to be dogmatic about design patterns or a particular architecture. Just do whatever makes sense and document it well.
In other words, decomposing a class into smaller classes because the larger class has multiple responsabilities is a good principle, but not a law. You don't want to follow it at any cost. Look at the pros and cons of applying it. Sometimes, decomposing a class into smaller classes gives you more control over responsaibilities, but at the expense of a great deal of communication among those classes. In your example, you might as well implement different classes for resource loading, caching, mapping, saving, etc. But if each class needs to talk to the others in order to perform its work, then the decomposition is not worth doing, because it entails high coupling, which is bad; keep it all in a single class.
Sometimes I think that design patterns have brought about as much damage as clarity to the software development world! :-)