Подходит ли это для выполнения макросов во время выполнения?

Я разрабатываю систему для выполнения макросов во время выполнения в автоматической тестовой среде. Макросы определены в xml и предназначены для настройки нескольких устройств / DUT для конкретного случая использования. Разбор xml этих макросов выходит за рамки этого вопроса.

Для создания макросов в коде я создал код, показанный ниже. В этом примере нет класса ‘Macro’, а скорее коллекция ‘Statement’s. Это можно рассматривать как одно и то же.

Макрос состоит из операторов, и некоторые операторы должны выполняться только при определенных условиях (например, IfStatement).

Для выполнения операторов объект контекста передается между операторами и условиями. Этот контекст затем может использоваться / изменяться операторами и проверяться условиями.

Для этого я использую 2 интерфейса: IStatement и ICondition:

  • Каждый оператор реализует IStatement — имея метод Execute (контекст объекта) — и пытается привести контекст (типа объекта) к одному или нескольким интерфейсам, которые требуются оператору. Некоторым операторам на самом деле не нужен контекст (например, DelayStatement)

  • То же самое и с Условиями, они реализуют ICondition — имея метод IsTrue (контекст объекта) — и приводят контекст к ожидаемым интерфейсам. Некоторые условия не нуждаются в контексте (например, True, False), но явно реализуют интерфейс.

Существует проект Core, который определяет основы макросов, но его можно расширить для более конкретных проектов. Объект контекста может варьироваться в зависимости от проекта и даже может иметь другой тип (например, Context, SpecificContext). Для каждого проекта можно добавить больше утверждений / условий.

У меня есть пара вопросов по этому поводу:

  • Подходит ли способ передачи контекста для такого случая использования? Должно ли утверждение / условие «знать» о контексте, который оно будет получать / ожидать? Или это должно быть ответственность вызывающего абонента?
  • Если да, то какой для этого подход лучше?
  • Должен ли я определять несколько интерфейсов для операторов, которые требуют контекста, и операторов, которые этого не делают? То же самое и с условиями.
  • Если эта передача контекста является шаблоном проектирования, как оно называется?
  • В заявлении / условии: следует ли мне проверять наличие нескольких интерфейсов / классов? (например, в DeviceConnectedCondition: должен ли я проверять, является ли контекст устройством, а затем проверять, является ли контекст IDeviceContainer, или я должен просто проверить наличие IDeviceContainer?)

Некоторые примечания:

  • Я могу создать интерфейс IContext, который имеет как минимум аргументы, сообщения и результат, чтобы уменьшить количество приведений.
  • Есть больше утверждений / условий, чем показано ниже, это просто песочница.
  • Я начал использовать шаблон Visitor и не использовал общий метод Execute (object) / IsTrue (object), но наткнулся на проблемы на этапе конкретных проектов, когда я не знал, как правильно расширить Core.Visitor. В этом случае только Посетитель знал о конкретном классе / объекте контекста и знал, какие методы вызывать для утверждений / условий (например: он знал, что условие True не требует контекста, но для SendI2CToDevice он только предоставил Device вызывая определенный метод)

Большое спасибо!

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);
        }
    }
}



```

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *