设计模式之模板方法模式(封装算法)

第一次设计

下面,我们的学习将从咖啡和茶的制作上开始进行。
泡咖啡的步骤:

  • 把水煮沸
  • 用沸水冲泡咖啡
  • 把咖啡倒入杯子
  • 加糖和牛奶

泡茶的步骤:

  • 把水煮沸
  • 用沸水侵泡茶叶
  • 把茶倒入杯子
  • 加柠檬

下面,用代码来实现上面的步骤:
咖啡的实现:

public class Coffee
    {
        void prepareRecipe()
        {
            boilWater();
            brewCoffeeGrinds();
            pourInCup();
            addSugarAndMilk();
        }

        public void boilWater()
        {
            Console.WriteLine("Boiling water");
        }

        public void brewCoffeeGrinds()
        {
            Console.WriteLine("Dripping Coffee through filter");
        }

        public void pourInCup()
        {
            Console.WriteLine("Pouring into cup");
        }

        public void addSugarAndMilk()
        {
            Console.WriteLine("Adding Sugar and milk");
        }
    }

茶的实现:

public class Tea
    {
        void prepareRecipe()
        {
            boilWater();
            brewTeaBag();
            addLemon();
            pourInCup();
        }

        public void boilWater()
        {
            Console.WriteLine("Boiling water");
        }

        public void brewTeaBag()
        {
            Console.WriteLine("Steeping the tea");
        }
        public void pourInCup()
        {
            Console.WriteLine("pouring into cup");
        }

        public void addLemon()
        {
            Console.WriteLine("Adding Lemon");
        }


    }

改进

从上面的代码可以发现,有两个步骤我们是重复了的,我们可以把重复的部分抽取出来,放入到一个基类中。
把boilWater()、pourInCup()提取出来放入基类,把prepareRecipe()定义成抽象方法。但这样定义,对于prepareRecipe()仍然需要实现两次,我们将boilWater()、pourInCup()泛化,使用brew()和addCondiments()来替代,并且在基类中,将其定义成抽象方法。这样prepareRecipe()不在抽象,而是在基类中实现。
基类:

    public abstract class CaffeineBeverage
    {
        public void prepareRecipe()
        {
            boilWater();
            brew();
            addCondiments();
            pourInCup();
        }

        public void boilWater()
        {
            Console.WriteLine("Boiling water");
        }

        public void pourInCup()
        {
            Console.WriteLine("pouring into cup");
        }

        public abstract void brew();

        public abstract void addCondiments();
    }

下面看一幅图,来看看我们现在都做了些什么:

什么是模板方法

模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现
在上面的代码中,我们模板方法就是prepareRecipe(),原因:

  • 它是一个方法
  • 它用作一个算法模板,在这个例子中,算法是用来制作咖啡因饮料
  • 在这里模板中,算法内的每一个步骤都被一个方法代表了。

定义模板方法模式

模板方法模式:在一个方法定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤

这个抽象类包含了模板方法。
primitiveOperation1()、primitiveOperation2()模板方法所用到的操作的抽象版本,模板方法本身和这两个操作的具体实现之间被解耦。
一个模板方法中可能许多具体类,这个具体类实现了抽象类的操作。

钩子(对模板方法进行挂钩)

钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类决定。
下面来看看代码怎么写:

    public abstract class CaffeineBeverageWithHook
    {
        void prepareRecipe()
        {
            boilWater();
            brew();
            poueInCup();
            if (customerWantsCondiments())
            {
                addCondiments();
            }
        }
        public abstract void brew();
        public abstract void addCondiments();
        void boilWater()
        {
            Console.WriteLine("Boiling water");
        }
        void poueInCup()
        {
            Console.WriteLine("Pouring into cup");
        }
        bool customerWantsCondiments()//这就是一个钩子,子类可以覆盖(这个方法通常是空的实现)
        {
            return true;
        }
    }

使用钩子

让我们看看它在实际代码里面的应用
子类继承:

    public class CoffeeWithHook : CaffeineBeverageWithHook
    {
        public override void addCondiments()
        {
           Console.WriteLine("Adding Sugar through filter");
        }
        public override void brew()
        {
           Console.WriteLine("Dripping Coffee through filter");
        }
        public override bool customerWantsCondiments()
        {
            Console.WriteLine("Adding Sugar through filter?Y/N");
            string s = Console.ReadLine();
            if (s=="y")
            {
                return true;
            }
            return false;
        }
    }

测试:

        static void Main(string[] args)
        {
            CoffeeWithHook coffeewithhool=new CoffeeWithHook();
            coffeewithhool.prepareRecipe();
            Console.ReadKey();
        }

关于钩子

什么时候使用钩子

当算法的某个实现可选的时候,可以使用钩子。当算法的某个实现是必须的时候,使用抽象方法

钩子的目的

钩子可以让子类有能力为其基类做一些决定。

好莱坞原则

好莱坞原则:别调用我们,我们会调用你。
好莱坞原则防止依赖腐败:当高层组件依赖底层组件,而底层组件又依赖高层组件,而高层组件又依赖边测组件,边侧组件又依赖高层组件,这样依赖腐败就发生了。
在好莱坞原则下,我们允许底层组件将自己挂钩到系统上,但高层组件会决定什么时候调用这些底层组件。

好莱坞原则应用

我们在之前设计模板方法的时候,其实就用到了好莱坞原则:

在上面图中,CaffeineBeverage就是我们的高层组件,它能够控制冲泡方法的算法,只有在需要子类实现某个方法是才调用,饮料的客户代码只依赖CaffeineBeverage的抽象,而不依赖具体的类。

好莱坞原则和依赖倒置原则

在这里我们看到好莱坞原则感觉和依赖倒置原则很像,都是用于解耦。
依赖倒置原则让我们尽量避免使用具体类,而多使用抽象,它更加注重与在设计中避免依赖。
好莱坞原则则是一种用在创建框架或组件上的一种技巧,好让底层组件能够被挂钩计算,而又不会让高层组件依赖底层组件,它是创建一个有弹性的设计,允许底层结构能够互相操作,而又防止太过于依赖。

使用模板方法来排序

使鸭子类继承至IComparable接口,之后使用sort方法进行排序

    public class Duck:IComparable
    {
        private string name;
        private int weight;
        public Duck(string name, int weight)
        {
            this.name = name;
            this.weight = weight;
        }
        public  string toString()
        {
            return name + "weighs" + weight;
        }
        public int CompareTo(object obj)//需要提供的实现
        {
            Duck otherDuck = (Duck) obj;

            if (this.weight<otherDuck.weight)
            {
                return -1;
            }
            else if (this.weight==otherDuck.weight)
            {
                return 0;
            }
            else
            {
                return 1;
            }
        }
    }

排序

        static void Main(string[] args)
        {
            List<Duck> ducks=new List<Duck>{new Duck("11",11),new Duck("2",2),new Duck("13",13),new Duck("7",7)};
            DisPlay(ducks);
            ducks.Sort();
            DisPlay(ducks);
            Console.ReadKey();
        }

在上面的例子中,我们可以看到子类提供的实现排序方法的算法。

原文地址:https://www.cnblogs.com/Tan-sir/p/8310899.html