-1

Background: I'm coding an app, the core idea is simple - I can choose a 'Task' (consists of name, code to perform aka Runnable, progress) through GUI, start it, stop it, start all 'Task's and stop all 'Task's. Each 'Task' has to be dispatched on different thread (long operations).

The problem: As mentioned, 'Task' class has ability to be started or stopped. Normally, I'd introduce a manager class like 'TaskManager' in order to literally manage tasks (let's say starting them on a different thread and keeping the handle in order to cancel it or keeping an eye on the threads amount - limited resources), but I've heard that Manager type of classes can be smelly and just poor practice.

Core question: Do I really need that kind of manager class?

0

2 Answers 2

2

The Manager (Anti)pattern

There is nothing wrong with a class whose purpose is to manage a collection of objects. For example, a database connection pool is a valid "DbConnectionManager", since it can improve application responsiveness while removing a lot of overhead from developers. And in some sense, a List<T> is a manager of objects of type T, where the objects don't require a lot of supervision.

However, the Manager (anti)pattern often gets a bad name for a number of reasons.

First, there's the issue of naming. Often, by taking the shortcut of tacking the word "Manager" onto a class name, we lose the opportunity to clearly describe the purpose of the new class. If I wanted to track the inventory of vehicles on an asphalt plane, I would much rather see a ParkingLot than a CarManager. The former much more clearly communicates the intended usage of the class, and offers other benefits that I describe below.

Second, managers tend to grow exponentially as the project grows. This is true for businesses and for the codebase itself. Once you start calling everything a Manager, you'll find it hard to stop. Before long, you'll need a ManagerManager to keep all of your managers in line.

By giving descriptive names from the start, and not defaulting to a generic name, we greatly weaken the stranglehold that managers have on our project. Well-named classes don't completely remove the possibility for an explosion of manager classes, but they can certainly help.

Third, it's much easier to adhere to the Single Responsibility Principle if we use descriptive names. Both a Dealer and a ParkingLot are responsible for managing vehicles, but I don't expect a parking lot to be selling me anything. We'll have more classes, but each of them will be more limited in scope and usage. Unfortunately, nothing's stopping a developer from throwing all that functionality and the kitchen sink into a CarManager. For this reason, we should avoid names that lend themselves to poor design practices.

Can we manage without a manager?

Get a Manager

With all of that being said, don't forget the first thing that I said:

There is nothing wrong with a class whose purpose is to manage a collection of other classes.

Whether you call it a "manager" or something else, you may still need something to keep track of your tasks. Do you need to query the status of tasks and display them on a UI, print to console, etc.? Then you need something to keep track of the list of available tasks. Again, this can be a simple List<Task> if that's what works for you.

Or Don't

On the other hand, sometimes we can get away with not actually having a single manager object keep track of a collection of tasks. Rather, we can let task manage its own state. This is often accomplished by means of events or method callbacks, and is useful in asynchronous programming when we don't when a task will end.

For example, in C# I can define events such as TaskStarted, TaskFailed, and subscribe to these events when the object is first created (or whenever I have a reference to the object, but this is harder to keep track of without a list of some sort). Example:

public class Task
{
    event EventHandler TaskStarted;
    event EventHandler TaskEnded;
}

// And in the wild
public void DoStartTask(object sender, EventArgs e)
{
    // Do stuff
}
public void DoEndTask(object sender, EventArgs e)
{
    // Remove references to event for garbage collection
    ((Task)sender).TaskStarted -= DoStartTask;
    ((Task)sender).TaskEnded -= DoEndTask;
    // Do other stuff
}

Task myTask = new Task();
myTask.TaskStarted += DoStartTask; 
myTask.TaskEnded += DoEndTask;
// Even if I don't add it to a list, the DoStartTask and DoEndTask will fire at the right time.

Class self-management of this type works best when the objects are truly self-managed. If you need to query a list of tasks, e.g. retrieve them from a datasource, you will probably have a better time with a single authority (i.e. a manager) than multiple individual self-managed tasks.

1
  • @DocBrown good catch, edited. Apr 8, 2022 at 12:09
1

The problem with *Manager classes is not that they are poor practice per se, but that they often turn into "god classes" which break the single responsibility principle - because nobody knows what the boundaries of a "manager" are, everything just gets thrown into one.

On the other hand, something that organises tasks sounds like a necessary part of your application - but try and see if you can break it down further than just a "manager". It's a bit tricky without further details of exactly what you want to do, but one possible split is into two components:

  1. A "task pool" which holds the handle/id/whatever for each task and allows it to be cancelled.
  2. A "task scheduler" which is responsible for taking tasks and actually starting them executing.

Not the answer you're looking for? Browse other questions tagged or ask your own question.