A pattern for choosing behavior based on the types present in the collection objects.

I have a set of objects that is a model of a system. Each of these objects derives from a base class that represents an abstract "component". I would like to be able to look at the system and select a specific behavior based on which components are present and in what order.

For the argument, let's name the base class Component

and the actual components InputFilter

, OutputFilter

and Processor

. The systems that we can deal with are those that have Processor

one or both filters. The actual system has more types and more complex interactions between them, but I think this will do it for now.

I see two "simple" ways to handle this situation with a function marshalComponentSettings()

that takes one of the collections and designs the most efficient setup for each node. This may require changing the inputs in a certain way, or splitting them up differently, so it's not as easy as simply implementing a virtual function handleSettings()

for each component.

  • The first is to communicate the enumerated type from each class using a pure virtual function, and use them to develop what to do dynamic_cast

    , where needed to access the component-specific parameters.

    enum CompType {
        INPUT_FILTER,
        OUTPUT_FILTER,
        PROCESSOR
    }
    
    void marshal(Settings& stg)
    {
        if (comps[0].type() == INPUT_FILTER)
            setUpInputFilter(stg); //maybe modified the stg, or provides other feedback of what was done
    
        // something similar for outputs
    
        setUpProcessor(stg);
    }
    
          

  • The second is dynamic_cast

    for anything that might be an option in that function, and use the success of that or not (and also possibly a cast object, if needed) to determine what to do.

    void marshal(Settings& stg)
    {
        if (InputFilter* filter = dynamic_cast<InputFilter*>(comp[0]))
            setUpInputFilter(stg); //maybe modified the stg, or provides other feedback of what was done
    
        // something similar for outputs
    
       setUpProcessor(stg);
    }
    
          

The first seems to be the most efficient way (no need to speculatively test each object to find out what it is), but even that is not entirely correct (perhaps due to the annoying details of how these devices affect each other, seeping into common marshaling code).

Is there a more elegant way to handle this situation than nest conditional behavior definitions? Or even the name of a situation or pattern?

+3


source to share


1 answer


Your script seems like an ideal candidate for a template , with the following roles (see UML diagram in link):

  • objectStructure: your model, aka collection Component

  • : your Component

    base class
  • concreteElementX: your actual components ( InputFilter

    , OutputFilter

    , Processor

    ...)
  • visitor: An abstract family of algorithms that should manipulate your model as a sequential collection of elements.
  • concreteVisitorA: your customization process.

Main advantages:

Your configuration / customization matches the intent of the design pattern: an operation performed on the elements of an object's structure. Conversely, this pattern allows for the order and appearance of elements encountered during crawling, since visitors can be stateful .

One positive side effect is that the visitor template will give you the flexibility to add new processes / algortihms with similar traversals but different purposes (ex: system evaluation, material scheduling, etc.)



class Visitor; 
class Component {
public:
    virtual void accept(class Visitor &v) = 0;
};
class InputFilter: public Component {
public:
    void accept(Visitor &v) override;  // calls the right visitor function
};
...
class Visitor
{
public:
    virtual void visit(InputFilters *c) = 0;  // one virtual funct for each derived component.
    virtual void visit(Processor *c) = 0;
    ...
};
void InputFilter::accept(Visitor &v)
{ v.visit(this); }
...
class SetUp : public Visitor {
private: 
    bool hasProcessor; 
    int  impedenceFilter;  
    int  circuitResistance; 
public: 
    void visit(InputFilters *c) override;  
    void visit(Processor *c) override; 
    ... 
};

      

Problem:

The main challenge you will have for the visitor, but with other alternatives, is that the customization can change the configuration itself (replacing the component replacement with an order of magnitude), so you have to take care of maintaining the iterator on the container without worrying about to process items multiple times.

The best approach depends on the type of container and what changes you are making. But you will definitely need some flags to see which item has already been processed, or a temporary container (processed items or remaining items to process).

In any case, since the visitor is a class, it can also encapsulate any such state data into private members.

+1


source







All Articles