Search code examples
c#design-patternsarchitecturecommanddecoupling

How to decouple mode switching and commands


How to decouple a Mode (normally expressed by enums) from its implementation in commands and their relationship? Is their a good pattern describing the loose binding between a mode switch (int, enum, string, ...) and its command calls? I want to add modes via config, so this must be (dynamically) easy extendable (without programming). I already know the command pattern (ICommand in C#/.Net). It could be an Dictionary of Commands and their related mode number, but what about the switching logic?


Solution

  • It is possible to decouple context (switching descission, parameters) from the strategy to solve the request (input) and its response (output).

    You could use e.g. generics to solve this problem for different cases with one code base. For example the strategy is defined by its kind of input and output and a condition to evaluate:

    public class Strategy<TInput, TOutput>
    {
        public Predicate<TInput> Condition { get; private set; }
        public Func<TInput, TOutput> Result { get; private set; }
    
        public Strategy(Predicate<TInput> condition, Func<TInput, TOutput> result)
        {
            Condition = condition;
            Result = result;
        }
    
        public TOutput Evaluate(TInput input)
        {
            return Condition(input) ? Result(input) : default(TOutput);
        }
    }
    

    The context has different strategies and asks the strategies for their responsibility (given conditions are ok, can calculate the result for an request).

    public class Context<TInput, TOutput>
    {
        private List<Strategy<TInput, TOutput>> Strategies { get; set; }
    
        public Context(params Strategy<TInput, TOutput>[] strategies)
        {
            Strategies = strategies.ToList();
        }
    
        public TOutput Evaluate(TInput input)
        {
            var result = default(TOutput);
            foreach (var strategy in Strategies)
            {
                result = strategy.Evaluate(input);
    
                if (!Equals(result, default(TOutput)))
                    break;
            }
    
            return result;
        }
    }
    

    Let's take a simple example where an input string needs to be resolved to an output string (like a switch case).

    1. Define your actions (for simplification this is an (as you can see, every Method has its own logic and concern):

        private static string Move(string s)
        {
            if (s == "move")
                return "doMove"; // here could be some more action...
            return null;
        }
    
        private static string Query(string s)
        {
            if (s == "point")
                return "queryPoint"; // here could be some more action...
            return null;
        }
    

    2. Define a condition to run the evaluation (lika a CanExecute of an ICommand):

        private static bool Condition(string s)
        {
            return !string.IsNullOrEmpty(s); // could eval different states, values
        }
    

    ... you could even define more conditions (e.g. one condition eval function per strategy) but we use only one here.

    3. Create strategy objects required by the context (these symbolize the different paths of the switch and they give us the result):

     var navigate = new Strategy<string, string>(Condition, Move);
     var query = new Strategy<string, string>(Condition, Query);
    

    2. Initialize your context (represents the switch body with the input):

     var strategies = new List<Strategy<string, string>> {navigate, query};
     var context = new Context<string, string>(strategies.ToArray());
    

    3. Wire them into the code (e.g. execute "switch" by a button click):

        private void ButtonClick(object sender, RoutedEventArgs e)
        {
            var message = context.Evaluate(textBox1.Text);
    
            if (message != null) MessageBox.Show(message); // display result
        }
    

    ... that's it.

    The context gives you the right strategy (can do the right actions or give the "tools" you need to do some actions (e.g. an ICommand).