tags:

views:

44

answers:

3

I have a model program that represents message flow along a chain of servers:

public class MyModel
{
    static bool x_send_msg1 = false; // has X sent msg1?
    static bool y_recv_msg1 = false; // has Y received msg1?

    static bool y_send_msg1 = false; // has Y sent msg1?
    static bool z_send_msg1 = false; // has Z received msg1?

    // (etc for more servers and more messages)

    [Action]
    static void YSendMsg1()
    {
       // Y sends Msg1 to Z
       y_send_msg1 = true;
       z_recv_msg1 = true;
    }
    static bool YSendMsg1Enabled()
    {
       // in the simplest case, this can happen whenever Y has received the
       // message but not yet forwarded it
       return y_recv_msg1 && !y_send_msg1;
    }

}

There are lots more messages. The Enabled() logic for each server and message type is slightly different, but the state is similar, so I would like to encapsulate it by writing something more like:

class State
{
    public bool send_msg1 = false;
    public bool recv_msg1 = false;
}
public static State X = new State();
public static State Y = new State();

and then use the encapsulated state in my actions:

[Action]
static void YSendMsg1()
{
  // instead of y_qqq above, now we can write Y.qqq:
   Y.send_msg1 = true;
   Z.recv_msg1 = true;
}
static bool YSendMsg1Enabled()
{
   return Y.recv_msg1 && !Y.send_msg1;
}

However NModel won't let me use objects in this fashion to hold my state. Is there some other way I can avoid defining repeating groups of booleans, one for each server in the chain?

A: 

I think the observer pattern might help you here - http://www.dofactory.com/Patterns/PatternObserver.aspx

Mark Cooper
If the NModel engine won't let me analyze a simple class/struct, why do you think it's going to let me analyze a multi-class pattern?
Eric
+1  A: 

Questions of style aside, the main benefit of encapsulating the state as shown in the question is to reduce the amount of code that must be written and read. Instead of having to write (#servers * #messages) declarations, only (#server + #messages) are required.

The same reduction in code (with corresponding improvements in readability and reduction in carpal tunnel syndrome) can be achieved by using NModel's built-in Set class to track the state of each message. A set called send_msg1 contains the names of all the servers that have sent msg1:

public class MyModel
{
    static set<int> send_msg1 = set<int>.EmptySet; // has server #n sent msg #1?
    static set<int> recv_msg1 = set<int>.EmptySet; // has server #n received msg #1?
    // (etc for more messages)

    static int X = 1;
    static int Y = 2;
    // (etc for more server names)

    [Action]
    static void YSendMsg1()
    {
       // Y sends Msg1 to Z
       send_msg1 = send_msg1.Add(Y);
       recv_msg1 = recv_msg1.Add(Z);
    }
    static bool YSendMsg1Enabled()
    {
       // in the simplest case, this can happen whenever Y has received the
       // message but not yet forwarded it
       return recv_msg1.Contains(Y) && !send_msg1.Contains(Y);
    }

}

(It is possible to reduce the amount of code event further, for example by using a map of sets to hold everything in a single variable. However one advantage of leaving the state patially separated is that it produces more legible state summaries in the model viewer.)

Eric
A: 

Since writing my answer above, I have learned of another solution that is closer to what I was originally looking for.

To use objects with instance variables, you can derive from LabeledInstance as shown below. The instances should be allocated using the static Create() method, and fields should be initialized in an overridden Initialize() method.

(You can also use instance methods for actions, but to do so you must assign a domain to the class; the name of that domain should correspond to a static set that contains all current instances of the class.)

Behind the scenes, NModel is going to translate this class into a bunch of maps, one for each instance variable in the class. The key into those maps will be indexed instances of the class name, e.g. State(1). This can be somewhat hard to read within the MVC tool, so you may also want to keep some kind of instance variable that contains a legible, consolidated summary of the object's state.

class State : LabeledInstance<State>
{ 
    public override void Initialize()
    {
        send_msg1 = false;
        recv_msg1 = false;
    }
    public bool send_msg1;
    public bool recv_msg1;
} 

public static State X = State.Create();
public static State Y = State.Create();
Eric