0
votes
\$\begingroup\$

Problem:

What is an effective design pattern to allow an end user to indirectly modify the state of data in an object based on a prioritized list of user specified modifers?

Explanation : This is very general of course, but hang in there for a second so I can elaborate - I'm not a software person by trade so I don't know the appropriate language to use. Say I have a class called 'Grid' who's purpose is to:

  1. Store a regular 2D Cartesian grid of 'colors'
  2. Allows the user to initialize all the colors to some state
  3. The Grid tracks the "evolution" of the colors. By this I mean the state of the data in the grid can change by some function Grid(n+1) = f( Grid(n) ). Where F( Grid(n) ) is a function that transforms a grid at step n to step n+1. An example of this could be

    if a node to my right is red and I am not red:

    then

    swap colors
    

    else

    do nothing
    

So a "grid" at step n that looks like this:

BLUE BLUE BLUE
BLUE BLUE RED
BLUE BLUE BLUE

Will look like this at step n+1

BLUE BLUE BLUE
BLUE RED BLUE
BLUE BLUE BLUE

And at n+2 this:

BLUE BLUE BLUE
RED BLUE BLUE
BLUE BLUE BLUE

I will leave this F( Grid(n) ) as an opaque process because this is not where I have a design issue - I do mention this so that some design decisions I make in the future will have some context.

  1. The user is allowed to modify the data at every step via a multiset that uses a comparison object based on a priority. The multset contains an object that somehow modifies the state of my grid of colors.

Ok What do I mean by this? Take a look :

class Grid {
        private:
                vector< vector< colors > > colorGrid; 
                multiset< shared_ptr< SetGrid >, comp > setRules; 
                // ... //
        public:
                Grid( dims d2 ); // dims is a typedef for pair<int,int>
                // ... //
                void addSetRule( shared_ptr< SetGrid > sg );
                // ... //
        void applyRules( void );
};

Where I have defined SetGrid to be an ABC:

class SetGrid {
        protected:
                int priority;
                colors c;
        public:
                SetGrid(colors clr ) : c(clr){};
                int getPriority() const;
                virtual void apply( vector< vector< colors > > & cg) const=0;
};

The 'comp' object in the multiset declaration compares priorities through the getPriority() member function. So now you may see whats going on. Classes Derived from SetGrid can change the state of coloGrid by Grid::applyRules() function:

void Grid::applyRules( ){

        multiset< shared_ptr< SetGrid >,comp >::iterator it;

        for (it = setRules.begin(); it != setRules.end(); ++it)
                (*it)->apply( colorGrid );

}

The multiset orders items it contains base on their priority. So at this point a user could derive a class from SetGrid to set all colors in a users specified column to RED and derive another class from SetGrid whos function is to set a subset of a row to GREEN after RED is set. Example:

G(n):

BLUE BLUE RED
BLUE RED BLUE
BLUE BLUE BLUE

setRules now contains a "set columns to RED" object with high priority and a "set a subset of a row to GREEN" with lower priority, so the state changes as follow:

set column to RED ( say I set column 0 to RED) :

RED BLUE RED
RED RED BLUE
RED BLUE BLUE

And now set a subset of a row GREEN (say row 1, items 0 and 1):

RED BLUE RED
GREEN GREEN BLUE
RED BLUE BLUE

And now we can apply our Function F( G(n) ) -> G(n+1) and repeat this process however many times we like or until something bad happens.

So that is the functionality I desire from this 'Grid' class - What is the name of the design pattern I have used/what is a better design pattern to achieve this goal? I feel like what I have done is acceptable - but I hope there is a more elegant solution that is more expressive/compact than what I have.

A full listing of my simple example is on this codepad page - Note that I compiled with g++4.5 -std=c++0x -Wall (no optimizations) and use things defined in the new c++11 standard. I did not put it here because in total the full code is ~160 lines, a little too much for the eyes on one page.

\$\endgroup\$

2 Answers 2

2
votes
\$\begingroup\$

Design pattern?

Thanks for your detailed question. Sorry, but what you're looking for is not a "design pattern". Look at this quote from Wikipedia (emphasis mine):

In software engineering, a design pattern is a general reusable solution to a commonly occurring problem within a given context in software design.

Your issue is probably not a commonly occurring one. More specifically, it probably hasn't occurred enough for someone to notice an useful pattern. I did have this issue once(!) at school, and the solution we opted for back then was way more complicated than what you have right now. (For example, we implemented flocking behavior in this Python code).

It's still possible that you'll use some design patterns in the solution. One can argue that a "rule-based system" is a design pattern. Rule-based systems are certainly used extensively in many fields with the same semantics. It also allows you to decouple the different rules since they are in their own class. In a sense, you used a design pattern here.

Also note that design patterns don't aim for code conciseness. For example, the Composite pattern involves using a lot of new classes. It does make the code better when properly used, though. Trying to get below 150 lines would probably involve using less classes, and would make your code harder to extend.

Code comments

  1. bool operator()( const shared_ptr< SetGrid > & lhs, const shared_ptr<SetGrid> & rhs){
    

    This is only one example to show that you should care more about spacing. < SetGrid > or <SetGrid>. I'd recommend <SetGrid>.

  2. Use strong enums.

  3. I'd recommend for using std::vector instead of vector, but that's up to you.
  4. Are you aware that you do not need a comp object, since operator< in SetGrid would be enough? This means you don't need to expose the priority, but also makes it less explicit that you are going to compare SetGrids. Up to you!
\$\endgroup\$
5
  • \$\begingroup\$ Thanks for the commmens - 1. what do you recommend? I like the spacing but of course it can get long with templated template parameters. 2. Changed it 3. std::vector where? std::vector in place of the multiset? 4. I forgot about operator <, that I can use. Thanks! \$\endgroup\$
    – Marm0t
    Mar 3, 2012 at 13:53
  • \$\begingroup\$ I was just thinking and realized I have to use an outside class for the comparison object in multiset, since it is templated for a shared_ptr ! oh well... it would have been nice to use a member function \$\endgroup\$
    – Marm0t
    Mar 3, 2012 at 14:12
  • \$\begingroup\$ I recommend <SetGrid>, ie. no spaces. Sorry, I had forgotten the "``", and markdown thought it was an html tag. For vector, I was referring to std::. I edited! \$\endgroup\$ Mar 3, 2012 at 14:52
  • \$\begingroup\$ Ah yes - once I break this out into its own source file all the std library components are going to be properly scoped. Otherwise would you say the design is sound/straightforward/suggest any other fundamental changes to my approach? \$\endgroup\$
    – Marm0t
    Mar 3, 2012 at 17:19
  • \$\begingroup\$ I do think you chose the right approach! :) \$\endgroup\$ Mar 3, 2012 at 20:35
1
vote
\$\begingroup\$

The command pattern describes your idea of encapsulating the various mutators as objects, but it doesn't address your priority queue.

As for the overall design:

  1. it isn't clear why the Grid has to own it's transient list of mutators: it creates a circular dependency (although that isn't particularly a problem), and if you plan to display this grid and want to use something like the Model/View/Controller (MVC) pattern, it's pulling controller state into the model.

  2. the SetGrid mutators operate directly on the grid's internal vector of vectors, meaning you're voluntarily breaking encapsulation for something whose (concrete) type you don't know

    • either encapsulation is useful here, in which case SetGrid::apply should just take a Grid& argument and call mutator methods on that object
    • or it isn't, and your Grid can be trivially decomposed into an essentially public vector of vectors, and something which manages and iterates over the priority queue of SetGrids

neither of those are problems that will stop it working, but the code doesn't feel that well aligned with what you're trying to express.

\$\endgroup\$
1
  • \$\begingroup\$ 1. Grid is never displayed - I just used it as an example that would make my problem easier to understand (actually has nothing to do with visualization/user interaction). 2.a - If I would take this route I would have to provide a public interface to the internal state of grid. 2.b Isn't my 'grid' interface that 'something' that stores the vectors and iterates over the mutators? Thanks for your feedback - I agree what I am doing doesn't quite feel 'natural' and am looking to improve - If you could post an edit of how you might suggest a change I would appreciate it. \$\endgroup\$
    – Marm0t
    Mar 6, 2012 at 1:28

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