dimanche 30 décembre 2018

Implementing different iteration strategies on a composite task

I'm trying to improve my OOP understanding with a (what I thought was) simple task system. However, the more I think about it the more grows in complexity. Considering the following example:

Download document:
 | Send an email to a manager that a new document has been downloaded (with the file name and size)
 | Process document:
    -> Create report

In this example downloading a document is considered an expensive task. The order of sending an email to a manager and processing document shouldn't matter. They can however only be completed when a document has been fully downloaded.

Based on my reasoning there are 4 types of tasks:

  1. Simple task (Single task)
  2. Compound task (Combine multiple tasks in a single task, all get processed in a single go)
  3. Series task (Multiple tasks, the order of execution matters, processing happens step by step)
  4. Parallel task (Multiple tasks, the order of execution doesn't matter)

Now when implementing it seems to make sense to use the composite pattern (See image). Using an interface/abstract class as the base and inherit from it to create a SimpleTask and a CompositeTask. Then use the CompositeTask as the base class for the Compound/Series/Parallel.

However, this forces the CompositeTask to be non extendable. My assumption is that this would be undesirable for tasks which have certain expensive tasks with dependencies. Consider the example in which the document has to be downloaded before sending an email / processing(*). Therefor it seems like a good idea to place the type of execution outside the CompositeTask (strategy pattern?). This is the part where I'm starting to struggle cause I probably want some form of hybrid iterator/strategy class, however I'm unsure how to implement this. It raises the questions:

  • Is the "iterator strategy" the actual way to go or is my reasoning about this wrong/ did I miss something?
  • Who owns who? Does the composite own the strategy or is it the other way around? ( $composite->useStategy(...) or $strategy->setTask(...) )
  • Who's responsible for keeping track of the tasks? The composite or the strategy?
  • Should the strategy just return a list of items that should get executed or should it actually invoke the individual tasks?

Ideally / in the final version I would like to be able to step through every task step by step and have a some form of progress list to see how many tasks have been completed (loading screen). Keeping this in mind only adds to the confusion.

My apologies in advance for my English (non-native) or if this question is too broad or vague but I really don't know how to be more specific about this (nor do I know any other place to ask this question).


(*) Not sure if this is the best way of achieving this. But having the CompositeTask extendable allows to easily inject dependencies based on a task subclass. It feels a bit clunky though, but for example:

class RequiresDocument extends Task {
    function setDocument(document) {...}
}

class LoadDocumentTaks extends CompositeTask {
    function run() {
        $document = $this->fetchDocument();

        foreach ($this->tasks as $task) {
            if ($task instanceof RequiresDocument::class) {
                $task->setDocument($document);
            }
        }
    }
}

Aucun commentaire:

Enregistrer un commentaire