Nickso
Nickso

Reputation: 805

Template Method and Remove If Statements

So, Im studying Design Patterns, and Im studying the Template Method.

From how I understood it, It is a set of Methods (The skeleton) wrapped in a Method (Operation) on an Abstract Class (If done via heritage), where different Concrete Subclasses write their own implementation of those methods (Not all of them).

But I have a doubt, what happens if some, maybe 2 methods of the skeleton are not used by a certain concretion?, Here I have an example I made, which totally violates the SRP:

using System;

namespace TemplatePattern
{
    public abstract class Coffee
    {

        public void MakeCoffee()
        {
            HeatWater();

            PutCoffee();

            if (HaveMilk())
            {
                PutMilk();
            }

            if (HaveGratedChocolate())
            {
                PutGratedChocolate();
            }

            PutSweetener();

            ServeCoffee();
        }

        internal void HeatWater()
        {
            Console.WriteLine($"I heated the water");
        }

        internal void ServeCoffee()
        {
            Console.WriteLine($"Coffee Served");
        }

        internal void PutCoffee()
        {
            Console.WriteLine("I put 2 spoons of Coffee");
        }

        internal virtual void PutMilk() { }

        internal virtual void PutGratedChocolate() { }

        internal abstract void PutSweetener();

        public virtual bool HaveMilk()
        {
            return false;
        }

        public virtual bool HaveGratedChocolate()
        {
            return false;
        }
    }
}

Concrete class SimpleCoffeeWithMilk:

using System;
namespace TemplatePattern
{
    public class SimpleCoffeWithMilk : Coffee
    {

        public override bool HaveMilk()
        {
            return true;
        }

        internal override void PutSweetener()
        {
            Console.WriteLine($"I put 1 spoon of Sugar");
        }

        internal override void PutMilk()
        {
            Console.WriteLine($"I put 100Cc of Milk");
        }
    }
}

Another Concrete class:

using System;

namespace TemplatePattern
{
    public class CoffeeWithChocolate : Coffee
    {
        public override bool HaveGratedChocolate()
        {
            return true;
        }

        internal override void PutGratedChocolate()
        {
            Console.WriteLine("Put Chocolate");
        }

        internal override void PutSweetener()
        {
            Console.WriteLine($"Put Sugar");
        }

    }
}

Main Entry Point:

using System;

namespace TemplatePattern
{
    class Program
    {
        static void Main(string[] args)
        {
            SimpleCoffeWithMilk coffeWithMilk = new SimpleCoffeWithMilk();
            CoffeeWithChocolate coffeeWithChocolate = new CoffeeWithChocolate();

            coffeWithMilk.MakeCoffee();

            Console.WriteLine("\n\n");

            coffeeWithChocolate.MakeCoffee();

            Console.ReadLine();

        }
    }
}

The idea is to get rid of those If's Statements, is there any way of doing this with the template method, where some of the methods execute depending of the concrete class?

I was thinking in creating interfaces like ICoffeeWithMilk with PutMilk() method on it and implement that interface on my SimpleCoffeeWithMilk concrete class, but watching the UMLs, the Template Method for what I saw does not rely on Interfaces.

Edit: Now that I think of it, I cant use an interface as, the template method relates to a set of ordered methods in the operation, so these methods are out of the operation.

Edit 2: Ok, I was thinking that PutMilk() and PutGratedChocolate() are Hook methods, maybe I can make them abstract methods and in the concrete classes dont put any implementation, not even the not implemented exception class. With this they can exists without any if statements in my Template Method. But I think, Im not sure this violates the Liskov Substitution Principle.

Edit 3: Well... I was thinking, again, and came to the conclusion that, if those methods are virtual and have no implementation on the abstract class, I shouldn't worry about asking, if the concrete class uses that method, then you write the algorithm, if you don't use it, then dont, and it will do nothing, it will go to the next step.

Upvotes: 0

Views: 347

Answers (2)

tallseth
tallseth

Reputation: 3665

Just remove the if statements and let the concretions make the decision by implementing them, like so:

public abstract class Coffee
{
    public void MakeCoffee()
    {
        PutMilk();
    }

    protected abstract void PutMilk();
}

public class SimpleCoffeWithMilk : Coffee
{    
    public override void PutMilk()
    {
        Console.WriteLine($"I put 100Cc of Milk");
    }
}

public class SimpleCoffeNoMilk : Coffee
{    
    public override void PutMilk()
    {
        //no op
    }
}

As to your concern with this solution, I don't think it violates Liskov. The LSP states that a subtype must be substitutable for any other inheritors of it's base class, which these cases are. You can call PutMilk for either, and they will put all of the milk that is appropriate to their specialization of the interface. None of that variation impacts the calling method. To be clear, you can contrive an example that would do that, but in this case you are not going to run into a problem there.

Upvotes: 1

M. Akar
M. Akar

Reputation: 1865

You can transfer the selection statement (if statement in this case) to your templates but cannot get rid of them if you need to check some condition. Weather you do it like this

public abstract class Coffee
{
    public abstract bool HaveMilk();

    public void MakeCoffee()
    {
        if (HaveMilk())
        {
            PutMilk();
        }
    }
}

public class SimpleCoffeWithMilk : Coffee
{
    public bool HaveMilk()
    {
        return true;
    }

    public override void PutMilk()
    {
        Console.WriteLine($"I put 100Cc of Milk");
    }
}

or like this

public abstract class Coffee
{
    public void MakeCoffee()
    {
        PutMilk();
    }
}

public class SimpleCoffeWithMilk : Coffee
{
    public bool HaveMilk()
    {
        return true;
    }

    public override void PutMilk()
    {
        if (HaveMilk())
        {
            Console.WriteLine($"I put 100Cc of Milk");
        }
    }
}

I strongly believe the latter is what you are looking for.

Upvotes: 0

Related Questions