views:

107

answers:

3

I want to write a simple class (PHP5) that can 'run' an unknown amount of subclasses. These subclasses are best translated as 'checks'; they all will more or less do the same thing and give an answer (true / false). Think of it as system startup checks.

Over time new checks (subclasses) will be added to a directory and they should automatically be run when the main class is invoked. Others may write checks but they will have to follow the functions dictated by the main class.

What would be a clean yet simple way to build this? I found the Factory pattern and combined with interface it seems a good starting point. However I am not sure, and I would appreciate advice on this.

EDIT: the provided answers here all work, however the answer by Gordon gives additional possibilities of batches and stacking, something I did not think of at the time of asking, but am now rather happy about.

+3  A: 

If you want to create a Batch-like functionality, use the Command Pattern.

Below is a very simple implementation of the pattern. The idea is to have a unified interface for all classes you want to invoke. Each class encapsulates one operation in the batch:

interface BatchCommand
{
    public function execute();
    public function undo();
}

One class implementing the interface will be the commander of all subclasses:

class BatchCommander implements BatchCommand
{
    protected $commands;

    public function add(BatchCommand $command)
    {
        $this->commands[] = $command;
    }
    public function execute()
    {
        foreach($this->commands as $command) {
            $command->execute();
        }
    }
    public function undo()
    {
        foreach($this->commands as $command) {
            $command->undo();
        }
    }
}

A simple Command could look like this:

class FileRename implements BatchCommand
{
    protected $src;
    protected $dest;

    public function __construct($src, $dest)
    {
        $this->$src;
        $this->dest;
    }

    public function execute()
    {
        rename($this->src, $this->dest);
    }

    public function undo()
    {
        rename($this->dest, $this->src);
    }
}

You could then use it like this:

$commander = new BatchCommander;
$commander->add(new FileRename('foo.txt', 'bar.txt'));
$commander->add(/* other commands */);
$commander->execute();

Because BatchCommander is a BatchCommand itself, you can easily stack batches belonging together into other Batches, thus creating a very flexible tree structure, e.g.

$batch1 = new BatchCommander;
$batch1->add(/* some command */);
$batch1->add(/* some other command */);

$batch2 = new BatchCommander;
$batch2->add(/* some command */);
$batch2->add(/* some other command */);

$main = new BatchCommander;
$main->add($batch1);
$main->add($batch2);
$main->execute();

In your Check/Test context, this means you could group conceptually belonging single tests into a test suite. And you can create test suites of test suites, by stacking one suite into another.

Of course, you could also give the BatchCommander a file path to check on instantiation and have it init all BatchCommands by running through the files in the file path. Or pass it a Factory instance to use for creating the Check commands.

You don't have to have execute and undo for method names. Name it check if you want. Leave out undo if you don't need it. The basic idea still stays the same though: one interface for all classes to be commanded, whatever that may look like. Feel free to adapt.

An alternative with a somewhat different UseCase is the Chain of Reponsibility pattern. Check it out to see if this would be of utility too.

Gordon
Thank you for your answer. Something I do not fully understand though; you write *One class implementing the interface will be the commander* and then start the code with *public function BatchCommander implements BatchCommand* Is that a typo or am I misunderstanding the principle?
Matt
@Matt that's on purpose. The BatchCommander commands all BatchCommands, but it is also a command itself. This allows you to create an arbitrary tree structure by grouping BatchCommands into a BatchCommander. I'll update the post for an example.
Gordon
Thanks again Gordon, I appreciate your time and additions. I indeed did miss the point (grouping and stacking) which I do grasp now (and I am excited about it :) ) However, I still do not understand where to _invoke_ the _public function batchCommander_ To my (limited indeed) knowledge this is a method, which should reside witin a class, but which class? If I use your example I get an *Parse error: syntax error, unexpected T_PUBLIC* error regarding the _public function_ line nr.
Matt
@Matt Gah! My fault. That's supposed to be `class BatchCommander` of course. Sorry.
Gordon
@Gordon: The Command pattern is very powerful, but maybe you don't have to go down that road until all that flexibility is really needed. I'm thinking YAGNI here.
Martin Wickman
@wic I've thought about this for ten minutes now and cannot think of anything that the Command pattern adds that wouldn't be needed. At least not, when this is going to be solved with clean and maintainable OOD.
Gordon
@wic Or let's rephrase my previous comment into a question: what do you think it adds that's yagni?
Gordon
@Gordon: Depends on the OPs requirements, but having an arbitrarily complex object graph with Commands could be considered a bit overkill in this case. Commands are for creating things like transactions, multiple undos, recording macros, repeating actions etc. So I think this case is better served with a simpler solution.
Martin Wickman
@Gordon: Not to dwell on this to much, but imo Command can be used to solve almost _any_ problem. It's like a big sledgehammer and everything tends to look like a nail then. Add a `IfCommand` and a `DoUntilCommand` and you have pretty much created your own Turing complete programming language. Don't go there :-)
Martin Wickman
@wic It seems a great approach for what I am attempting. This is indeed a _testing application_ where a complete suite like SimpleTest et all seems a bit overkill to deploy. I am certainly no pattern expert but I like this approach for my situation.The way I understand Gordon's example class it quite easily allows _dynamic_ batches of checks, stack these batches, and being able to run a batch of tests only if another batch succeeded. My original approach would have been a lot more 'run all checks in this directory' but I asked because I know that approach will bite me down the line.
Matt
@wic Hmm, I don't think that's a fair comparison. The Golden Hammer Antipattern (sledgehammer/nail analogy) occurs through the *misapplication* of a favored tool or concept. That doesn't make the *concept itself* an Antipattern though. Also, if *being able to stack* is already overkill, you just could just remove the interface from the BatchCommander. That's the only thing that enables it. The remainder of the class is basically a foreach loop in a method. How could this be any simpler?
Gordon
@Matt: I think you ought to update the question text. It says nothing about "dynamic batches, stacking and condition". Given those requirements, I'm definately in favor of Gordons approach! My arguments against the Command solution is just wrong given this new information. Instead it seems to be a good application of that design pattern. +1
Martin Wickman
Matt
+1  A: 

I guess the issue here is how you find which classes you should instantiate and run, since you need to know the name of the class.

I suggest that you load each *.php file in the directory and use a simple naming convention. For instance, the name of the file (minus extension) is the name of the class. Then just:

 // foreach $file in glob("*.php"):
    $classname = basename($file, ".php");
    $instance = new $classname(); 
    $result = $instance->runCheck();

Where runCheck() is the actual method that each class must provide.

You did not say anything about running checks in any particular order, but that could be remedied by adding a nn_ prefix to each file and load them in order. For instance 05_CheckFoo.php.

Martin Wickman
That's somewhat inefficient. If the files follow a naming pattern (like you suggested), e.g. `01_individual-filename.checkcommand.php`, he can better do `glob('*.checkcommand.php')` instead of going through all the files.
Gordon
Fixed, although I don't think shaving of a few milliseconds is the problem here :)
Martin Wickman
A: 

Check class autoloading, so you don't have to take care of how many subclasses exist. PHP will take of everything: http://php.net/manual/en/language.oop5.autoload.php

dusoft
You still need to know the actual name of the class to load though, and for that you need to check the existing files anyway (or maintain a separate list of classes).
Martin Wickman