views:

147

answers:

3

I am generating a sequence of Step objects that differ by "Type" and data contained within. e.g:

The Step objects should basically be structs that look like this

{ GRAB, CASCADE_ONE, FACEUP, SOMEOTHERDATA },
{ DROP, DECK,  FACEDOWN, MOREDATA, ANDSOMEMORE },
{ MOVE, 34, 89 },

where GRAB, MOVE and DROP indicate StepType:

typedef enum
{
     GRAB,
     DROP,
     MOVE
}StepType;

As you can see, depending on StepType, these structs each have variable numbers of data fields after the StepType.

I plan to iterate over a sequence of these structs and perform a particular action based on the StepType field. My first hunch is that these should be objects of classes derived from an abstract Step class - i.e. I should create a GrabStep class, a MoveStep class and a DropStep class.

Is this a good design and if so should I create them using a factory method? If a factory method is the way to go, then how to initialise the fields within the objects?

+1  A: 

From the sounds of it all you need is polymorphism of some kind, which you're onto with the abstract base class.

The factory pattern is when the caller don't care about the type of the object they're going to need, only that it conforms to an interface. For instance, a parser of a config file that doesn't care where the config file lives or if it's binary or xml. All the parser wants to do is read elements.

In your case whatever code is choosing which step type to construct by definition cares about which object type they're creating. You can still use the factory pattern to abstract away the construction, but you're going to need one factory function (with the relevent parameters) per step type.

Depending on what methods you use to decide which step type to construct, you may want to abstract them away into a strategy pattern or similar.

tenpn
+4  A: 

You don't need the factory pattern for this. But creating an abstract Step class is a good start:

class Step
{
private:
    // The presence of a pure virtual makes this class abstract.
    virtual void DoAction() = 0;
public:
    virtual ~Step() {} // Needed if you are going to delete via a Step* pointer
    void Action() { DoAction(); } // Template method pattern
};

// All other classes derive publicly from Step, since they all have an "is-a"
// relationship with Step (i.e. a GrabStep "is-a" Step).
class GrabStep : public Step
{
private:
    void DoAction() { /* Do whatever a GrabStep does */ };
    // Data relevant to GrabStep
};

class MoveStep : public Step
{
private:
    void DoAction() { /* Do whatever a MoveStep does */ };
    // Data relevant to MoveStep
};

class DropStep : public Step
{
private:
    void DoAction() { /* Do whatever a DropStep does */ };
    // Data relevant to DropStep
};

Then, you can iterate over these things without having to know their exact types:

// Example:
std::vector<Step*> seq; // or some other container
// Note that we are storing Step* pointers in a container instead of Step
// objects. This is needed for polymorphism to work.
// ...
seq.push_back(new GrabStep);
seq.push_back(new MoveStep);
seq.push_back(new DropStep);
// ...
for(std::vector<Step*>::iterator i = seq.begin(); i != seq.end(); ++i)
{
    // Will call the proper version of DoAction() depending on the actual type.
    (*i)->Action();
}
// ...
// After we are done, clean up after ourselves. This is needed because
// std::vector does not delete the pointees.
for(std::vector<Step*>::iterator i = seq.begin(); i != seq.end(); ++i)
{
    delete (*i); // Safe because Step has a virtual destructor.
}
In silico
`Step` usually needs a virtual destructor.
Philipp
+1 for the private virtual interface.
tenpn
@In silico - how should the `DropStep`, `GrabStep` classes inherit from `Step`- privately or publicly?
BeeBand
@In silico - I can't compile the `Step* i` you have in your for loop. Should that be an iterator?
BeeBand
@BeeBand - `public` inheritance, since `DropStep`, `GrabStep` and `MoveStep` have an `is-a` relationship with `Step`. Also, yes, you should be using an iterator.
In silico
+1 - good answer, polymorphism is the right solution in this case.
Matthew Iselin
+1  A: 

If you need run-time polymorphism, then I think it's a good design. Regarding the factory function: if your clients need one, then write one. The factory function simply calls the constructors of the subclasses.

Philipp