Argument against "manager" classes

Started by
52 comments, last by Zahlman 17 years, 4 months ago
Quote:Original post by T1Oracle
So does the STL. Ever heard of remove_if, copy_if, transform, for_each? The STL has plenty of useful algorithms.


Yes - but thats not what I meant. It doesn't perform operations ON the list, but WITHIN it. Take my entity manager, every time you create an entity it needs to have its spawn timer set, and it needs to have its spawn script run. If I didn't have an enitty manager I'd have to either put that code manually in every function which created an entity (a pain in the butt), or I'd have to write a standalone function todo it. Why wouldn't I put that function (and the rest like it) with my entity "manager"? My manager also, adds the item to the list and does memory management etc, but its most usefull functions are the specifics which no STL algorithm can do because they are unique.


Quote:Original post by T1Oracle
A name is a brief yet meaningful summary of what an object does.


Exactly - my managers "manage" my resources :P I don't see how a "factory" or "flyweight pattern" is any more descriptive than "manager".



Quote:Original post by T1Oracle
Then how does it make any less sense to define the entire application as a manager.


lol - true, I'd also say my IDE manages my code an is therefor a manager. And because I manage the IDE i'm its manager. yay - infinite loop of joy. But that doesn't take away from the fact my resource managers manage my resources ...



Quote:Original post by T1Oracle
What about versatility, loose coupling, and code size? How much of those "managers" aren't even being used? How much code are you writting just to deal with the specific querks of a given "manager?" How much work would you have to do to add in a feature not supported by that "manager?" How much refactoring would be needed to swap one "manager" for another?


- My managers are about 300-500 lines long on average ... I'd say about 80% of that is the actual resource loading (which you have to put somewhere so is unavoidable).
- I use all my managers - otherwise I wouldn't still keep them around.
- What do you mean specific quirks? The whole point of managers is to DEAL with the quirks of a chosen resourse so I'd have to say 100% of it is for quirks.
- Less code because off the manager. I'd only have to add it inside the manager, and expose a function or two to use it. If it wasn't inside a manager who knows how much code I'd have to tie in from random places to use it.
- No refactoring would be needed. My managers all return int ID's, and accept int ID's to todo whatever it is they do so I'd only every need to change the manager. I did this too ages ago when I change from .tga loading to .png - only change my texture manager and no other code.


Quote:Original post by T1Oracle
When you get the term "manager" what exactly are you guaranteed?


Like I said - thats not a downside in my oppinion. I know exactly what I'm garenteed by MY managers ... if I gave you my class called "ManagerTexture" with has a int load(std:string fname); then i'm pretty sure you can tell what it does. If you give me a class called "FlyweightTexture" then I'd probably assume it loaded really small textures :P
Advertisement
After fully reading all links posted and the definitions of those design patterns I didn't already know (read: flyweight) I still won't be renameing my classes. This is actual a style war. We both hate bad code with poor design, yet I don't see the name of a class as a factor in that - you do. Its not that I like vague classes with alot of useless and unrelated junk in them, its that I wouldn't write a manager like that so don't see the two as related! I'll grant you that if someone knows enough to follow a design patter then its harder to screw up, but I'm sure there are alot of EntityFactorie classes out ther with alot of bloat :P

I can see where your comming from, but I still say bad code isn't related to the term manager, its related to knowledge, skill and experience.



Oh and in keeping with all style wars: Curly brackets on a new line 4EVA!!!
The main problem with "managers" is that everybody means a bit a different thing; there is no common definition of that term. So the term is widely used, and perhaps we understand something different. But these are my thoughts about that:

Managers implemented for resources like textures, sound, and so on normally provide a client transparent caching perhaps with a policy like LRU, and they may hide whether resources are loaded from individual files, archives, or the network. This is true additional functionality compared to containers available from STL. IMHO managers in this sense are okay. Whether you name this class a ResourceManager or a ResourcePool makes no real difference.

The above has something of the Factory pattern, correct. A Manager implemented by the Mediator pattern, well, I personally hasn't such a need yet, but perhaps that comes from my understanding of what a Manager is.

Coming to more esoteric thoughts, it is good practice to hide clients from implementation details. E.g. in Java there is a LinkedList class implementing the more general List class, itself implementing the even more general Collection class. Well, Collection and List are abstract and LinkedList can be instantiated (is an _implementation_ class). Recommended practice is to declare the member at most as a List and initializing it with a LinkedList (as an example). Granting client access to it should be done, if necessary at all, by an Iterator. Notice please that also std::list is an implementation class, and a (well written) Manager encapsulates its internally used std::list by generalizing the access without exposing the inner implementation. Generalizing accesses allows exchange without touching clients.

Maybe managers are overused, but that cannot mean that they are senseless.

Quote:Original post by kaysik
I hate people who just stick to design patterns for the sake of it as if somehow that'll make them write good code. Code that has a fancy name is not better code (infact in my experience is usually worse).

Of course, using patterns just to use them isn't good practice. IMHO, the benefit of knowing (enough) patterns is that in practice one notices problems to be of the one or other kind since they look like the typical problem of the one or other pattern, and hence can be solved in a way at least similar to what the matching patterns suggest. Effective programming is mostly based on experience, and patterns are a kind of "tinned experience". Knowing patterns allows a more effective discussion of problem solutions; I see that weekly in our company.
Quote:Original post by OrangyTang
*genious indepth sense and understanding*

Thank you, I agree on all of your points.
Quote:Original post by kaysik
Take my entity manager, every time you create an entity it needs to have its spawn timer set, and it needs to have its spawn script run.

Sounds like something that needs to be deligated down to the entity objects themselves. Either that or you need a EntityFactory which makes it clear what is being down with entities by this object. The EntityFactory is creating entities, it is not doing anything (on the user end) outside the scope of creating them. It may do some background stuff necessary during creation, but it not called on after creation.


Quote:Original post by kaysik
Exactly - my managers "manage" my resources :P I don't see how a "factory" or "flyweight pattern" is any more descriptive than "manager".

Factories create objects, flyweights perform operations on them. These two constructs are not the same thing and most likely should not be.

Quote:Original post by kaysik
resource managers manage my resources ...

Which means? Oh yea "whatever you want."

Quote:Original post by kaysik
- No refactoring would be needed. My managers all return int ID's, and accept int ID's to todo whatever it is they do so I'd only every need to change the manager. I did this too ages ago when I change from .tga loading to .png - only change my texture manager and no other code.

What if you wanted to use something other than int ID's? You'd have to refactor all of your managers. What if you wanted resource loading to be handled through a packed resource file? Then you would have to refactor all of your managers since they are tightly coupled to your current IO methods. What if you wanted to employ a memory pool? You would have to refactor all affected managers to allow for resource pools. As the details change the refactoring will only get more complex and eventually you will forget what everything does. At some point (given that you work on anything large) the actual function of the objects within your software will escape you.


Quote:Original post by kaysik
Like I said - thats not a downside in my oppinion. I know exactly what I'm garenteed by MY managers ... if I gave you my class called "ManagerTexture" with has a int load(std:string fname); then i'm pretty sure you can tell what it does. If you give me a class called "FlyweightTexture" then I'd probably assume it loaded really small textures :P

What does a "TextureFactory" do? The same exact thing that an "EntityFactory" does except for textures. If you need a texture object, then the "TextureFactory" will create on.

What does a "TexturePool" do? The same exact thing that a "MeshPool" does except for textures. If you need a texture and you don't want duplicate copies of the same texture in RAM, then acquire it from the pool.

If you had a fixed and restricting definition for what a "Manager" within your given application is. Then you can make it into your own pattern and it will be just as legitimate. It may not have a great name, but a defined meaning is the important fundamental here. Names should carry meaning, and objects have a defined domain over which they operate. Just as OrangyTang said:
Quote:Original post by kaysik
1. FooManager is an invitation for other programmers to lump in anything even slightly foo-related into the same class. Before long you've got a massive behemoth of a class that's doing everything and anything.

That is a recipe for bad design. However, if you give "manager" a specific definiton for your system and follow it, then you can evade that problem.

Perhaps the real issue here is:
1) Names, no matter how you spell them, should have well defined unambiguous meanings.
2) Objects and methods should not be lumped together simply because they affect the same set of classes. Objects and methods should be put together according to have they function, and how they vary.

*Edit, quotes fixed sorry OrangyTang*
Programming since 1995.
Quote:Original post by T1Oracle
I appologize if any of this sounds mean, my intent is not malice.

Quote:Original post by Skizz
tracks a collection (or collections) of objects and performs actions

Key phrase "peforms actions," those actions are very important. In fact the most important detail of a class to the user is not what it contains but what it does with it. "Performs actions" is a very non-descript (read "ambiguous) way to cause trouble.
Quote:Original post by Skizz
It stores them in an octree (or whatever structure you want)

Awesome, and now your "RenderManager" is coupled to collision detection and/or view frustrum culling.

Furthermore, think about the phrase "whatever structure you want." So, what again is a "RenderManger"? Oh yea a "RenderManger" is "whatever structure you want."

The point I was making was that somewhere you'd want a class that organsised a collection of objects in such a way that, in my example, rendering them was efficent. It has to be done somewhere. It can't be decoupled - it is the thing doing the spatial organisation. Something has to organise it. I called it RenderManager because it organises the objects within it and renders them. How it does it is totally decoupled from users of the class.

The dictionary definition of manager includes:
Quote:
a person who controls and manipulates resources and expenditures, as of a household.

which does match my RenderManager example. The RenderManager is a class that controls and manipulates objects(resources).


Quote:
Quote:Original post by Skizz
As for object IDs - use GUIDs rather than ints. GUIDs are guaranteed to be unique (and DevStudio has a tool to create them

Why can't an int be a GUID?

GUID has a very specific meaning. It's a 128-bit number hashed from various sources (MAC address, time, etc...). Windows has built in support for creating them. You can probably get libraries for other OSes. Your ID generation is problematic in that it will cause unusual effects when used in networked environment (multiplayer games) or possibly in multithreaded scenarios. GUIDs are globally unique.

Skizz
Quote:Original post by Julian90
class AClassWithAID{public:    AClassWithAID():    ID((int)this)    {    }    int ID() { return mID }private:    int ID;}


Now every ID is garuanteed to be unique :-)


A very bad way of doing it.
ObjectWithID *obj1 = new ObjectWithID;delete obj1;AnotherObjectWithID obj2 = new AnotherObjectWithID; // this will very likely have the same address!

How would an observer know if the object it is observing was destroyed? Sometimes another object can be created and have the same ID.

Skizz
Quote:Original post by Skizz
Quote:Original post by Julian90
class AClassWithAID{public:    AClassWithAID():    ID((int)this)    {    }    int ID() { return mID }private:    int ID;}


Now every ID is garuanteed to be unique :-)


A very bad way of doing it.
ObjectWithID *obj1 = new ObjectWithID;delete obj1;AnotherObjectWithID obj2 = new AnotherObjectWithID; // this will very likely have the same address!

How would an observer know if the object it is observing was destroyed? Sometimes another object can be created and have the same ID.

Skizz


I know i was wondering how long it would take for someone to point out how atrocious it was.

Another flaw is as follows:

as i recall int is defined as an integer with at least 2 bytes and at most 4 bytes however on a 64 bit system one object may be allocated at 0x0000000000000001 and another at 0x0000000100000001 which when cast to an int they will most lickly both end up as 0x00000001. :-)

It also suffers the same network problems as mentioned above
Quote:Original post by Skizz
Quote:Original post by T1Oracle
I appologize if any of this sounds mean, my intent is not malice.

Quote:Original post by Skizz
tracks a collection (or collections) of objects and performs actions

Key phrase "peforms actions," those actions are very important. In fact the most important detail of a class to the user is not what it contains but what it does with it. "Performs actions" is a very non-descript (read "ambiguous) way to cause trouble.
Quote:Original post by Skizz
It stores them in an octree (or whatever structure you want)

Awesome, and now your "RenderManager" is coupled to collision detection and/or view frustrum culling.

Furthermore, think about the phrase "whatever structure you want." So, what again is a "RenderManger"? Oh yea a "RenderManger" is "whatever structure you want."

It can't be decoupled - it is the thing doing the spatial organisation. Something has to organise it.

Yes it can be decoupled and by your example there I sounds like it very well should be. Collision detection and rendering are not the same thing. Afterall, why don't you just make one large class that does everything and call it Game?

Regardless, decoupling the tree from the renderer is trivial. Just have a renderer that takes a BVH (bounding volumen hierarchy which is what an octree is), a camera, and renders the visible geometry when RenderScene() is called. The render can even own instances of those objects.

Regardless, the render is a "renderer" not a manager. The other parts needed in the process of rendering are delegated down to separate objects. There is no reason why this must add a performance penalty. It may even make things faster as the actually problem space will be clearer with proper design.

The issue here is "layering."
Quote:Original post by Skizz
The dictionary definition of manager includes:
Quote:
a person who controls and manipulates resources and expenditures, as of a household.

which does match my RenderManager example. The RenderManager is a class that controls and manipulates objects(resources).

Program: Software that controls and manipulates data.

Quote:Original post by Skizz
GUID has a very specific meaning. It's a 128-bit number hashed from various sources (MAC address, time, etc...). Windows has built in support for creating them. You can probably get libraries for other OSes. Your ID generation is problematic in that it will cause unusual effects when used in networked environment (multiplayer games) or possibly in multithreaded scenarios. GUIDs are globally unique.

Skizz

The meaning of "globally unique" depends on the scope of the application. MS have its own definition and implementation, but it not absolute necessity that you application follows that definition nor is there any guarantee that Microsoft's GUID will be optimal for your specific needs.

The actual usage of the ID is very important in determining its proper use. Throwing GUID's at everything that needs searchable identy is not only bad design but it may also cost you performance (time calculating a GUID, RAM storing 128 bits when 32 or any other lesser value will do).

Furthermore, there is no such thing as a truly global unique identifyer that is unique over all world wide possible instances. At some point a GUID algorithm will produce the same number twice. See the tread on random numbers.

Simpler more optimal solutions are possible given a more specific problem. For one thing, a networked may not even need the MAC address to be a factor in the hash, the player's login name may be more than adequate.
Programming since 1995.
There are certainly situations where an object performing managerial duties on a collection of other objects is appropriate and correct. Yes, sometimes managerial objects are mis- or over-used (just like singletons; in fact these often go hand-in-hand), but that doesn't speak to a flaw in the concept of a managing entity.

When it is correct to use a managing entity, for example, it is not correct to replace that entity with a SC++L container, because in doing so you both violate encapsulation (as you even said: "an STL container with 0 encapsulation") or make it impossible to enforce invariants that must be maintained on the set of objects as a whole, such as actions performed when objects are added to or removed from the set (which is usually not functionality that can be placed into the objects themselves without tightly coupling the objects to the manager, because as you mentioned, tight coupling is generally bad) or maintaining a certain ordering in the case of an orderable container.

Furthermore the bulk of your arguments, when they aren't against mis- or over- use (which again is hardly the fault of concept itself, rather the programmers using it), seem to be about nomenclature; the fact that the term "manager" has no concrete description about what, exactly, the managerial duties actually are.

Well, yes. But the same problem applies to "factory," (it constructs things, but who knows how?), "pool," (it is some kind of shared/common collection of things, but who knows what kind of collection?), "resource," "engine," et cetera. Just about any common term has some element of ambiguity unless placed in the proper context. The best you can hope from the term "manager" is the same thing you can hope from those terms and others: "something that performs a certain kind of controlling or guiding action on a set of things." What those actions are are context-specific, and unless you start using stupidly long type names, that will always be the case, so I don't really see what the problem is.

Note that this is not an argument that "FooManager" is an acceptable name (it may not be, as OrangyTang pointed out). This is an argument that entities performing managerial functions are not inherently "evil." Picking type names that aren't stupid is an entirely different issue.
Quote:T1Oracle said:
Yes it can be decoupled and by your example there I sounds like it very well should be. Collision detection and rendering are not the same thing. Afterall, why don't you just make one large class that does everything and call it Game?

I have never, in any of my posts in this thread, mentioned collision detection. Why then do you assume I am including collision in my argument? That is something you have added to my example and then used as a counter argument. Octrees can be used for many things other than collision (sparse three dimensional arrays, for example).

To expand my case (to include collision!):

RenderManager: Handles spatial organisation of objects, efficently draws visible objects.

CollisionManager: Handles spatial organisation of objects, efficently handles collision detection.

The organisation of the objects does not need to be the same (and probably won't be). The set of objects managed by the RenderManager does not have to be the same of those managed by the CollisionManager. Missiles could, for example, be renderable objects but not collidable objects (i.e. nothing can hit them). They do collide against other objects so do use the CollisionManager. Invisible force fields are collidable but not renderable. They could use a common, shared storage. They could use separate storage. They could use a another class to store data, they could implement their own system. It really doesn't matter. The details are hidden from users of the managers.

To emphasise my use of manager: ThingManager only does actions to objects related to Thing, so RenderManager only renders things (nothing else, at all, no collision, no AI, no anything else!), CollisionManager only does collision, AIManager only does AI related things, ResourceManager only does resource handling. I find this use of "Manager" to be useful - a thing that handles a group of related things.

Skizz

This topic is closed to new replies.

Advertisement