Search code examples
c#design-patternsstrategy-pattern

Strategy Pattern with Dummy concrete strategy


Refering to posted question here, could you please comment if this is good approach to solve OPTIONAL behaviour for extending class, by using composition, not inheritance. Plannable behaviour is here extended by Strategy pattern.

So, class Task could optionally have any combination of various behaviours. Plannable is just one of them, therefore inheritance does obviously not making sense here.

Question is what to do when Task does not have particular behaviour ? I see few possible approaches:

  1. Instantiate concrete strategy for each task, and implement "Dummy" strategy when Task is not Plannable (this alternative is shown below). There are weird nullable Start and Finish variables all over the code, in this case...
  2. Having nullable IPlanningStrategy variable in the case task won't be planned, and instantiate with concrete Strategy only when it is "promoted" to be Plannable.

Alternative (1) should be something like this:

public class Task
{
    public string Title { get; set; }

    private IPlanningStrategy planningStrategy;

    public Task()
    {
        planningStrategy = new NoPlanStrategy();
    }

    public Task(IPlanningStrategy initialPlanningStrategy)
    {
        planningStrategy = initialPlanningStrategy;
    }

    public void SetPlanningStrategy(IPlanningStrategy newPlanningStrategy)
    {
        planningStrategy = newPlanningStrategy;
    }

    public DateTime? Start { get { return planningStrategy.Start; } }
    public DateTime? Finish { get { return planningStrategy.Finish; } }
}


public interface IPlanningStrategy
{
    public void CalculatePlan();
    public DateTime? Start { get; }
    public DateTime? Finish { get; }
}

// "Dummy" strategy, used when Task does not have Planning behaviour
//
public class NoPlanStrategy : IPlanningStrategy
{
    public void CalculatePlan() { }
    public DateTime? Start { get { return null; } }
    public DateTime? Finish { get { return null; } }
}



public class PlanStrategyA : IPlanningStrategy
{
    private int parameter1;
    private int parameter2;
    private DateTime? start;
    private DateTime? finish;

    public PlanStrategyA(int p1, int p2)
    {
        parameter1 = p1;
        parameter2 = p2;
        start = finish = null;
    }

    public void CalculatePlan()
    {
        // ... uses parameter1 & parameter2
        // ... to calculate start and finish
    }

    public DateTime? Start { get { return start; } }

    public DateTime? Finish { get { return finish; } }
}

public class PlanStrategyB : IPlanningStrategy
{
    public int parameter3;

    // ... the rest is similar to PlanningStrategyA

}

Now I see different ** VERY IMPORTANT** problem here. Each of my concrete Strategy class holds, beside algorithm, which is of course shared for all tasks implementing this algorithm, additional parameters, which belongs only to particular Task.

You can imagine parameter1 as Effort (or, say, number of remaining hours required to complete task), and say parameter2 as LastDate (representing constraint date, ie. last allowable date to finish task). This parameters naturally belongs to particular Task, but only if it implements this particular StrategyA.

It seems that instantiate strategy outside Task class does not make sense ? Or this should be job for some Factory method ?


Solution

  • IMO you are exposing too much information in your Task class. I would do as follows:

    public class Task
    {
        // what is title being used for?
        public string Title { get; set; }
    
        private IPlanningStrategy planningStrategy;
    
        public Task(IPlanningStrategy initialPlanningStrategy)
        {
            // Initialize it outside of constructor
            if(initialPlanningStrategy == null)
            {  
               throw new NullArgumentException(); // or return. 
            }
            planningStrategy = initialPlanningStrategy;
        }
    
        public void CalculatePlan(){
            // check null and return.
            planningStrategy.CalculatePlan();
        }
    
    }
    

    Your client shouldnt know start, finish, i dont see that as responsibility of a container for algorithms.

    Moreover, if your NoPlanStrategy not doing anything, why introduce it. removeit.