Skip to main content
added 261 characters in body
Source Link
  • Is the way of passing context a good way for a use case like this? Should the statement/condition 'know' about the context it is going to receive/expect? Or should that be the responsibility of the caller?
  • If so, what is a better approach for this?
  • Should I define multiple interfaces for Statements that require context, and statements that do not? Same goes for conditions.
  • If this context-passing is a design pattern, what is the name of it?
  • Within a statement/condition: should I check for multiple interfaces/classes? (e.g. in the DeviceConnectedCondition: should I check if the context is a Device and then check if the context is a IDeviceContainer, or should I just check for IDeviceContainer?)
  • Is the way of passing context a good way for a use case like this? Should the statement/condition 'know' about the context it is going to receive/expect? Or should that be the responsibility of the caller?
  • If so, what is a better approach for this?
  • Should I define multiple interfaces for Statements that require context, and statements that do not? Same goes for conditions.
  • If this context-passing is a design pattern, what is the name of it?
  • Is the way of passing context a good way for a use case like this? Should the statement/condition 'know' about the context it is going to receive/expect? Or should that be the responsibility of the caller?
  • If so, what is a better approach for this?
  • Should I define multiple interfaces for Statements that require context, and statements that do not? Same goes for conditions.
  • If this context-passing is a design pattern, what is the name of it?
  • Within a statement/condition: should I check for multiple interfaces/classes? (e.g. in the DeviceConnectedCondition: should I check if the context is a Device and then check if the context is a IDeviceContainer, or should I just check for IDeviceContainer?)
Source Link

Is this a good design for runtime Macro execution?

I am developing a system for runtime macro executions in an automatic test environment. Macros are defined in an xml and are for configuring multiple devices/DUTs for a specific use case. The xml parsing of these macros is outside of the scope of this question.

For building the macros in code I created the code shown below. In this example, there is no 'Macro' class, but rather a collection of 'Statement's. This can be regarded as the same thing.

A macro consists out of statements and some statements are only to be executed under certain conditions (e.g. IfStatement).

For execution of the statements a context object is passed between the statements and the conditions. This context can then be used/modified by the statements and checked by the conditions.

For this I use 2 interfaces: IStatement and ICondition:

  • Each Statement implements IStatement - having an Execute(object context) method - and tries to cast the context (of type object) to one or more interfaces which the statement needs. Some statements do not actually need the context (e.g. DelayStatement)

  • The same goes for the Conditions, they implement ICondition - having an IsTrue(object context) method - and cast the context to expected interfaces. Some conditions do not need the context (e.g. True, False), but implement the interface explicitly.

There is a Core project, that defines the basics of the macros, but this can be extended for more specific projects. The context object can vary per project and can even have a different type (e.g. Context, SpecificContext). More statements/conditions can be added per project.

I have a couple of questions about this:

  • Is the way of passing context a good way for a use case like this? Should the statement/condition 'know' about the context it is going to receive/expect? Or should that be the responsibility of the caller?
  • If so, what is a better approach for this?
  • Should I define multiple interfaces for Statements that require context, and statements that do not? Same goes for conditions.
  • If this context-passing is a design pattern, what is the name of it?

Some notes:

  • I can create an interface IContext, that has at least the Arguments, Messages, and Result to reduce some castings.
  • There are more statements/conditions than shown below, this is just a sandbox.
  • I started out using the Visitor pattern and no common Execute(object)/IsTrue(object) method, but stumbled upon problems in the stage of the Specific projects where I did not know how to properly extend the Core.Visitor. In this case, only the Visitor knew about the specific Context class/object and knew which methods to invoke on the Statements/Conditions (e.g: it knew that a True condition does not need context, but for the SendI2CToDevice, it only provided the Device by invoking a specific method)

Many thanks!

using ContextPassing.Core;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ContextPassing.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Please write a random text and press enter.");
            string randomText = Console.ReadLine();
            Console.Clear();

            Console.WriteLine("Do you want to send the the text to the DUT/Device ? (y for yes)");
            string printInput = Console.ReadKey().KeyChar.ToString();
            Console.Clear();

            Console.WriteLine("If previous answer was yes, do you want to add a delay before sending it to the DUT/Device ? (y for yes)");
            string addDelay = Console.ReadKey().KeyChar.ToString();
            Console.Clear();

            var statements = new List<IStatement>();

            statements.Add(new SetFieldStatement { FieldName = "RandomText", Value = randomText });
            statements.Add(new SetFieldStatement { FieldName = "SendInput", Value = printInput });
            statements.Add(new SetFieldStatement { FieldName = "AddDelay", Value = addDelay });

            var printInputIfStatement = new IfStatement() { Condition = new FieldCondition { FieldName = "SendInput", Value = "y" } };

            var elseif = new ElseIf { Condition = new FieldCondition { FieldName = "SendInput", Value = "k" } };
            elseif.Statements.Add(new LogStatement { Message = "This is an easter egg." });

            printInputIfStatement.ElseIfs.Add(elseif);
            printInputIfStatement.Else.Add(new LogStatement { Message = "Input was not sent." });

            var addDelayIfStatement = new IfStatement() { Condition = new FieldCondition { FieldName = "AddDelay", Value = "y" } };

            addDelayIfStatement.Statements.Add(new DelayStatement() { Time = TimeSpan.FromSeconds(1) });

            printInputIfStatement.Statements.Add(addDelayIfStatement);

            printInputIfStatement.Statements.Add(new LogFieldStatement() { FieldName = "RandomText" });

            printInputIfStatement.Statements.Add(new ContextPassing.Specific.SendI2cBytesToDevice() { Bytes = Encoding.ASCII.GetBytes(randomText) });


            statements.Add(printInputIfStatement);

            var context = new ContextPassing.Specific.SpecificContext();
            context.Device = new ContextPassing.Specific.Device();
            new StatementExecutor().Execute(statements, context);

            Console.WriteLine("*** Execution complete - Message overview: ***");

            foreach (string msg in (context as IMessageContainer).Messages)
            {
                Console.WriteLine(msg);
            }

            Console.WriteLine("***");

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}


namespace ContextPassing.Core
{
    public interface IArgumentContainer
    {
        IEnumerable<object> Arguments { get; }
    }

    public interface IFieldContainer
    {
        IDictionary<string, object> Fields { get; }
    }

    public interface IMessageContainer
    {
        ICollection<string> Messages { get; }
    }

    public class Context : IArgumentContainer, IMessageContainer, IFieldContainer
    {
        public Collection<object> Arguments { get; } = new Collection<object>();
        public Collection<string> Messages { get; } = new Collection<string>();
        public Dictionary<string, object> Fields { get; } = new Dictionary<string, object>();
        object Result { get; set; }
        IEnumerable<object> IArgumentContainer.Arguments => Arguments;
        ICollection<string> IMessageContainer.Messages => Messages;
        IDictionary<string, object> IFieldContainer.Fields => Fields;
    }

    public class StatementExecutor
    {
        public void Execute(IEnumerable<IStatement> statements, object context)
        {
            foreach (IStatement statement in statements)
            {
                statement.Execute(context);
            }
        }
    }

    public interface IStatement
    {
        void Execute(object context);
    }

    public interface ICondition
    {
        bool IsTrue(object context);
    }

    public class True : ICondition
    {
        public bool IsTrue() => true;
        public bool IsTrue(object context) => IsTrue();
    }

    public class False : ICondition
    {
        public bool IsTrue() => false;
        public bool IsTrue(object context) => throw new NotImplementedException();
    }

    public class Not : ICondition
    {
        ICondition Condition { get; set; }
        public bool IsTrue(object context) => !Condition.IsTrue(context);
    }

    public class Or : ICondition
    {
        Collection<ICondition> Conditions { get; } = new Collection<ICondition>();
        public bool IsTrue(object context) => Conditions.Any(x => x.IsTrue(context));
    }


    public class FieldCondition : ICondition
    {
        public string FieldName { get; set; }
        public object Value { get; set; }
        public bool IsTrue(object context)
        {
            var fieldContainer = context as IFieldContainer;

            if (fieldContainer is null)
                throw new InvalidOperationException("Context needs to be an IFieldContainer.");

            return fieldContainer.Fields[FieldName].Equals(Value);
        }
    }


    public class IfStatement : IStatement
    {
        public Collection<IStatement> Statements { get; } = new Collection<IStatement>();
        public Collection<ElseIf> ElseIfs { get; } = new Collection<ElseIf>();
        public Collection<IStatement> Else { get; set; } = new Collection<IStatement>();

        public ICondition Condition { get; set; }

        public void Execute(object context)
        {
            if (Condition.IsTrue(context))
            {
                foreach (IStatement statement in Statements)
                    statement.Execute(context);

                return;
            }

            foreach (ElseIf elseIf in ElseIfs)
            {
                if (elseIf.Condition.IsTrue(context))
                {
                    foreach (IStatement statement in elseIf.Statements)
                        statement.Execute(context);

                    return;
                }
            }

            if (Else != null)
            {
                foreach (IStatement statement in Else)
                    statement.Execute(context);
            }            
        }
    }

    public class ElseIf
    {
        public ICondition Condition { get; set; }
        public Collection<IStatement> Statements { get; } = new Collection<IStatement>();
    }

    public class SetFieldStatement : IStatement
    {
        public string FieldName { get; set; }
        public object Value { get; set; }

        public void Execute(object context)
        {
            var fieldsContainer = context as IFieldContainer;

            if (fieldsContainer is null)
                throw new InvalidOperationException("Context needs to be an IFieldContainer.");

            fieldsContainer.Fields[FieldName] = Value;
        }
    }

    public class LogStatement : IStatement
    {
        public string Message { get; set; }
        public void Execute(object context)
        {
            var msgsContainer = context as IMessageContainer;

            if (msgsContainer is null)
                throw new ArgumentException("Context needs to be an IMessageContainer.");

            msgsContainer.Messages.Add(Message);
        }
    }

    public class LogFieldStatement : IStatement
    {
        public string FieldName { get; set; }
        public void Execute(object context)
        {
            var fieldContainer = context as IFieldContainer; 
            var msgsContainer = context as IMessageContainer;

            if (fieldContainer is null)
                throw new InvalidOperationException("Context needs to be an IFieldContainer.");
            if (msgsContainer is null)
                throw new ArgumentException("Context needs to be an IMessageContainer.");

            msgsContainer.Messages.Add($"Field {FieldName} its value is: {fieldContainer.Fields[FieldName]}");
        }
    }

    public class DelayStatement : IStatement
    {
        public TimeSpan Time { get; set; } = TimeSpan.FromSeconds(1);

        public void Execute()
        {
            Thread.Sleep(Time);
        }

        // This statement does not need any context.
        void IStatement.Execute(object context) => Execute();
    }
}

namespace ContextPassing.Specific
{
    public class Device
    {
        public void SendI2cBytes(byte[] bytes) => Console.WriteLine($"Sending bytes bleep bloep blaap: {BitConverter.ToString(bytes)}");
        public bool IsConnected => true;
    }

    public interface IDeviceContainer
    {
        Device Device { get; }
    }

    public class SpecificContext : ContextPassing.Core.Context, IDeviceContainer
    {
        public Device Device { get; set; } = new Device();
    }

    public class IsConnected : ICondition
    {
        public bool IsTrue(object context)
        {
            var dc = context as IDeviceContainer;
            if (dc is null)
                throw new ArgumentException("Context should be a device container.");

            return dc.Device.IsConnected;
        }
    }

    public class SendI2cBytesToDevice : IStatement
    {
        public byte[] Bytes;

        public void Execute(object context)
        {
            var dc = context as IDeviceContainer;
            if (dc is null)
                throw new ArgumentException("Context should be a device container.");

            dc.Device.SendI2cBytes(Bytes);
        }
    }
}



```