I have a class that models LogicalExpressions. The leaves are classes that implement an interface IEvaluable, that has a method called Evaluate which returns a boolean as the result.

public class MyEvaluable : IEvaluable
{
    public bool Evaluate(Environment env)
    {

    }
}

Some of these evaluable objects need to do some heavy stuff to produce the result, like calling a web service for instance. And since a logical expression may have multiple such objects that are related, I would like to evaluate them all at once, doing one web service call for all of them instead of separate calls for each parameter.

So I've been thinking about a good way to design such a system and came up with 2 solutions:

1) Make my evaluable objects mutable.

public interface IBatchEvaluable
{
    void BatchEvaluate(object[] siblings, Common.Environment env);
    bool IsEvaluated { get; }
    bool EvaluationResult { get; }
}
public interface IBatchEvaluable<T> : IBatchEvaluable
{
    void BatchEvaluate<T>(T[] siblings, Common.Environment env);
}

So every object that is IBatchEvaluable will have a state. When I need to evaluate it I check if it's already been evaluated and do the batch evaluation if it's needed. The only con is that my objects will be mutable, and that's not really desirable.

2) Store evaluation data in the environment

I could keep the objects immutable, and move the IsEvaluated and EvaluationResult data in the environment. So each object would look in the environment first to see if it has already been evaluated and if so get the result from the environment, otherwise evaluate all the siblings in one go and put the data in the Environment.

This is also not very attractive, since the implementation of my objects will depend on outside data, doesn't seem to abide by OOP principles.

How should I go about this from an OOP perspective ? I am open to hearing other potential solutions to this.

  • Can you uniquely identify each of those requests? – Euphoric Jan 16 '17 at 15:29
  • Having an example of 2 leafs that interacts with a service would help. I'm really trying to picture how they could all get evaluated in a single service call if leafs are encapsulated. How do you know that every leaf will invoke the same service in the first place? Is this an assumption you can make for your design? Perhaps I misunderstood what you are trying to achieve. Are you just trying to avoid re-evaluating the same expressions? – plalx Jan 16 '17 at 15:38
  • I can identify the objects that use the same service by their type. It's like querying a table in a database for information about n objects. It's faster to make 1 query for all of them than to make n transactions with 1 query each. I'm not trying to avoid redundancy here, I need this to be fast, and make 1 service call instead of 10 when I can. – Adrian Jan 16 '17 at 15:43
  • @Adrian Would evaluating these nodes require the value of preceding nodes? For instance, could you scan the whole tree for these kind of nodes in order to resolve them all at once before even starting to evaluate from the root? – plalx Jan 16 '17 at 15:50
  • 1
    @Adrian Well in that case you could just run a pre-processor on your tree, gather all the service calls and execute them in one go or in as little roundtrips as you can, then replace the nodes in the tree with actual value nodes and evaluate the whole thing. – plalx Jan 16 '17 at 15:54

And since a logical expression may have multiple such objects that are related, I would like to evaluate them all at once, doing one web service call for all of them instead of separate calls for each parameter.

So why don't you do that?

Going through and memoizing the results of the expressions isn't going to be ideal when you're making calls to web services and the such since they're invariably going to be non-pure calls. If you ask for the temperature now, the web service is going to give you a different result than it would an hour from now. By moving to this batching approach under the covers, you're changing the result of your computation invisibly from your user.

If your goal is to get all of the data and then operate on it, then do that. What you describe is MapReduce in a nutshell. Instead of having logical expressions on individual items, you would shift the design to having operations that work on sets. Then it becomes more natural to get the data as a batch and perform operations on that batch. You don't need to worry so much about if parts of the data have been loaded since it becomes clearer in the set operation world. And with a bunch of existing implementations, you can find resources to help, or perhaps find that you don't need to write the code at all because it already exists.

  • That is what I am planning to do, and I know that it won't be 100% accurate, but it's a compromise I have to make. The question was not how to do it, the question was what was the best way to design this from an Object-Oriented perspective. – Adrian Jan 16 '17 at 16:00
  • @Adrian - I am open to hearing other potential solutions to this. Well, here is another potential solution. Good OO design is one that limits those sort of compromises. – Telastyn Jan 16 '17 at 16:07

One approach you could go with is to use composition and the strategy pattern. This will not eliminate mutability it will address the concern about encapsulation that you point to at the end of your question. Basically add one more layer over your Evaluable. It will have a reference to another Evaluable which is either one that can be resolved, or a result. When your composite Evaulable is called, you get the result from the resolvable Evaluable and put it in a result object.

If you want to be completely agnostic, you can simply do this with every Evaluable regardless of whether it's already resolved. This will create a churn of new objects but that may not be an issue. If it is, you can add a method to the Evaluable to return the result as a Evaluable and the resolved result simply returns this

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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