Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory class using generics but without base class

Ive got what I think may be an unusual problem (Ive searched around a lot for an answer, but I dont think Ive found one).

I have messages that are read from a queue and depending on the message type contains a payload that needs to be deserialized into a concrete c# class. This needs to eventually be concrete (I cant use generics the whole way) because Im using Expression Trees to perform Evaluations on the classes that arrive from the queue.

The base class looks like this:

public abstract class BaseRuleMessage<T>  
{
    public abstract Func<T, bool> CompileRule(Rule r, T msg);

    public T Deserialize(ClientEventQueueMessage message)
    {
        return JsonConvert.DeserializeObject<T>(message.Payload);
    }        

    public BaseRuleMessage()
    {
        RulesCompleted = new List<int>();
    }

    public IEnumerable<Rule> FilterRules(RuleGroup ruleGroup)
    {
        return ruleGroup.Rules.Where(item =>
            !RulesCompleted.Any(r => r.Equals(item.Id)));
    }

I implement the base class like this:

 public class UiTransactionUpdate : BaseRuleMessage<UiTransactionUpdate>
{

    public override Func<UiTransactionUpdate, bool> CompileRule(Rule r, UiTransactionUpdate msg)
    {
        var expression = Expression.Parameter(typeof(UiTransactionUpdate));
        Expression expr = BuildExpr(r, expression, msg);
        return Expression.Lambda<Func<UiTransactionUpdate, bool>>(expr, expression).Compile();
    }
    public Guid TransactionId { get; set; }

    public Guid GroupId { get; set; }

    public decimal StatusValue { get; set; }

I then do something like this to call:

 switch (message.MessageType)
            {
                case "UI_UPDATE":
                {
                    message.Payload = RemoveNullGroupIdReference(jsonPayload, message.Payload);
                    var deserializedMessage = new UiTransactionUpdate().Deserialize(message);
                    deserializedMessage.RulesCompleted = deserializedMessage.RulesCompleted ?? new List<int>();

                    foreach (var rule in deserializedMessage.FilterRules(ruleGroup))
                    {

What I really want to know is how can I create a factory (or can I?) to be able to define the implementation of the base class in such a way that I can return a concrete class to use for my expression tree evaluations without having to repeat all the calling code for each type.

like image 711
KerSplosh Avatar asked Oct 30 '22 18:10

KerSplosh


1 Answers

I avoided using dynamic but this meant that I had pass the object around as an object. I prefer not to use dynamic but in this case, casting objects at run-time may not be any better.

I also had to change the code so that instead of returning a Func<T, bool>, there is a method that would execute the Func. This was to avoid referring to the generic class. I'm not sure if you actually need the Func in your actual implementation.

I had to create a new base class that wasn't generically typed.

// Horrible name, do change it to something more appropriate
public abstract class BaseBaseRuleMessage
{
    public IList<int> RulesCompleted { get; set; }

    public IEnumerable<Rule> FilterRules(RuleGroup ruleGroup)
    {
        return ruleGroup.Rules.Where(item =>
                !RulesCompleted.Any(r => r.Equals(item.Id)));
    }

    public BaseBaseRuleMessage DeserializeToBaseBaseRuleMessage(ClientEventQueueMessage message)
    {
        return (BaseBaseRuleMessage) DeserializeToType(message);
    }

    protected abstract object DeserializeToType(ClientEventQueueMessage message);

    public abstract bool ExecuteRule(Rule rule, object msg);
}

Updated the BaseRuleMessage to derive from BaseBaseRuleMessage (and moved some properties to the base class.

public abstract class BaseRuleMessage<T> : BaseBaseRuleMessage
    where T : BaseRuleMessage<T>
{
    public abstract Func<T, bool> CompileRule(Rule r, T msg);

    protected override object DeserializeToType(ClientEventQueueMessage message)
    {
        return JsonConvert.DeserializeObject(message.Payload, typeof(T));
    }

    protected BaseRuleMessage()
    {
        RulesCompleted = new List<int>();
    }

    public override bool ExecuteRule(Rule rule, object msg)
    {
        var message = (T) msg;
        if (message == null)
        {
            throw new InvalidOperationException();
        }
        return CompileRule(rule, message).Invoke(message);
    }
}

The concrete class is basically the same. I've implemented my own BuildExpr to make sure the code can compile.

public class UiTransactionUpdate : BaseRuleMessage<UiTransactionUpdate>
{
    public override Func<UiTransactionUpdate, bool> CompileRule(Rule r, UiTransactionUpdate msg)
    {
        var expression = Expression.Parameter(typeof(UiTransactionUpdate));
        Expression expr = BuildExpr(r, expression, msg);
        return Expression.Lambda<Func<UiTransactionUpdate, bool>>(expr, expression).Compile();
    }

    public Guid TransactionId { get; set; }

    public Guid GroupId { get; set; }

    public decimal StatusValue { get; set; }

    private Expression BuildExpr(Rule rule, ParameterExpression parameterExpression, UiTransactionUpdate message)
    {
        var transactionIdProperty = Expression.Property(parameterExpression, "TransactionId");
        var value = Expression.Constant(rule.TransactionId);

        return Expression.Equal(transactionIdProperty, value);
    }
}

To use it:

var messageTypeToTypeMap = new Dictionary<string, Func<BaseBaseRuleMessage>>
{
    {"UI_UPDATE", () => new UiTransactionUpdate()}
};

var factoryFunc = messageTypeToTypeMap[message.MessageType];
message.Payload = RemoveNullGroupIdReference(jsonPayload, message.Payload);
var ruleMessage = factoryFunc.Invoke();
var deserializedMessage = ruleMessage.DeserializeToBaseBaseRuleMessage(message);
deserializedMessage.RulesCompleted = deserializedMessage.RulesCompleted ?? new List<int>();

foreach (var rule in deserializedMessage.FilterRules(ruleGroup))
{
    var isTrue = deserializedMessage.ExecuteRule(rule, deserializedMessage);
}
like image 88
Howwie Avatar answered Nov 15 '22 06:11

Howwie